@o3co/ts.hocon 1.13.0 → 1.14.1

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.
Files changed (43) hide show
  1. package/README.ja.md +253 -29
  2. package/README.md +161 -29
  3. package/dist/adapters/env.cjs +26 -2018
  4. package/dist/adapters/env.d.cts +1 -1
  5. package/dist/adapters/env.d.ts +1 -1
  6. package/dist/adapters/env.js +11 -1965
  7. package/dist/adapters/json5.cjs +15 -1969
  8. package/dist/adapters/json5.d.cts +1 -1
  9. package/dist/adapters/json5.d.ts +1 -1
  10. package/dist/adapters/json5.js +12 -1929
  11. package/dist/adapters/jsonc.cjs +17 -1972
  12. package/dist/adapters/jsonc.d.cts +1 -1
  13. package/dist/adapters/jsonc.d.ts +1 -1
  14. package/dist/adapters/jsonc.js +9 -1926
  15. package/dist/adapters/properties.cjs +7 -2113
  16. package/dist/adapters/properties.d.cts +1 -1
  17. package/dist/adapters/properties.d.ts +1 -1
  18. package/dist/adapters/properties.js +6 -2075
  19. package/dist/adapters/toml.cjs +12 -1966
  20. package/dist/adapters/toml.d.cts +1 -1
  21. package/dist/adapters/toml.d.ts +1 -1
  22. package/dist/adapters/toml.js +10 -1929
  23. package/dist/adapters/yaml.cjs +20 -1975
  24. package/dist/adapters/yaml.d.cts +1 -1
  25. package/dist/adapters/yaml.d.ts +1 -1
  26. package/dist/adapters/yaml.js +10 -1929
  27. package/dist/chunk-GOXMKYWS.js +3801 -0
  28. package/dist/chunk-JVABBNF5.js +158 -0
  29. package/dist/chunk-L3D2RE2U.cjs +3801 -0
  30. package/dist/chunk-OUPKYWL3.cjs +158 -0
  31. package/dist/chunk-PCAMDXJ4.cjs +83 -0
  32. package/dist/chunk-YRNDXYGQ.js +83 -0
  33. package/dist/{config-6sApTEMM.d.cts → config-OccaKqXu.d.cts} +126 -4
  34. package/dist/{config-6sApTEMM.d.ts → config-OccaKqXu.d.ts} +126 -4
  35. package/dist/index.cjs +34 -3793
  36. package/dist/index.d.cts +2 -2
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.js +25 -3728
  39. package/dist/zod.cjs +195 -3636
  40. package/dist/zod.d.cts +86 -3
  41. package/dist/zod.d.ts +86 -3
  42. package/dist/zod.js +182 -3589
  43. package/package.json +5 -5
package/README.ja.md CHANGED
@@ -10,8 +10,14 @@
10
10
  > **[Claude](https://claude.ai/)(Anthropic)による実装** — Claude Code を用いて設計・実装されました。
11
11
  > [GitHub Copilot](https://github.com/features/copilot) および [OpenAI Codex](https://openai.com/index/openai-codex/) によるレビュー。
12
12
 
13
+ **ドキュメント:** [o3co.github.io/hocon](https://o3co.github.io/hocon/) — 4 実装・hocon2 CLI・共通の準拠コーパスをまとめた横断ハブです。
14
+
13
15
  [English](README.md)
14
16
 
17
+ > **ライブラリの位置づけ:** ts.hocon は HOCON の設定ローダーです。`.hocon` 設定ファイルを読み、Config API(`getString`、`getNumber`、`getBoolean`、`getDuration`、`getBytes`、`getPeriod`、`toObject`)で型付きのアクセスを提供することが目的で、低レベルのパーサー API ではありません。`HoconValue` などの内部型はマイナーバージョン間で変わることがあります。
18
+ >
19
+ > **言語横断の準拠:** この実装は [o3co/xx.hocon](https://github.com/o3co/xx.hocon) の共通の期待値 JSON fixture に対して、[go.hocon](https://github.com/o3co/go.hocon)、[rs.hocon](https://github.com/o3co/rs.hocon)、[py.hocon](https://github.com/o3co/py.hocon) と並べてテストされており、4 つの実装が同じ Lightbend HOCON 仕様を満たすことを確かめています。
20
+
15
21
  ---
16
22
 
17
23
  ## クイックスタート
@@ -32,10 +38,11 @@ import { parse } from '@o3co/ts.hocon' // ESM
32
38
  const { parse } = require('@o3co/ts.hocon') // CJS
33
39
  ```
34
40
 
35
- CI では PR ごと・リリースごとに、ビルド成果物に対して 7 つの export すべてを両形式で
36
- ロードし、実際に呼び出し(バンドル形式で挙動が変わる package include を含む)、各条件が
37
- 自前の型定義を宣言しているかまで検証します。1.10.0 で CJS の 7 エントリポイントすべてが
38
- ロード時に例外を投げた実績があるためのゲートであり、この種の欠陥を検出するものです
41
+ CI では PR ごと・リリースごとに、ビルド成果物に対して 8 つの export すべてを両形式で
42
+ ロードし、実際に呼び出し(バンドル形式で挙動が変わる package include を含む)、サブパスが
43
+ 返す値や投げるエラーが root の export するクラスのインスタンスであること、各 export 条件が
44
+ 自前の型定義を宣言していることまで検証します。1.10.0 で当時の CJS の 7 エントリポイントすべてが
45
+ ロード時に例外を投げたことがあるためのゲートであり、この種の欠陥を検出するものです
39
46
  (あらゆる欠陥が起こり得ないという保証ではありません)。
40
47
 
41
48
  ### 2. 使い方
@@ -72,6 +79,49 @@ HOCON は単なるシリアライズ形式ではなく、**プログラムに注
72
79
 
73
80
  加えて HOCON は YAML の可読性と JSON の構造性を兼ね備えるため、フラットなキーバリュー設定を超えるユースケースには強い選択肢になります。
74
81
 
82
+ ## モジュールの中に閉じる設定
83
+
84
+ 大規模なコードベースでは、設定は漏れ出しがちです。全チームが編集する 1 つの大きなファイル、サービスごとにコピーされたデフォルト値、互いのキーを読み合うモジュール。HOCON なら、各モジュールがコードと同じように自分の設定を持てます。
85
+
86
+ - **各モジュールが名前空間を持ち、自分のデフォルト値を同梱する。** `auth` モジュールは `auth { … }` をコードの隣に置き、デフォルト値をすべて埋めておきます。アプリケーションはそのデフォルト値をコピーせず、`include` 1 行で取り込みます。
87
+ - **アプリケーションは違うところだけを上書きする。** 後のレイヤーが前のレイヤーにディープマージされるので、アプリケーションが書くのはブロック全体ではなく 2 つのキーだけです。
88
+ - **各モジュールは自分のサブツリーだけを、自分のスキーマで読む。** モジュールは `auth` を要求し、自分で定義したスキーマで検証します。そのセクションの形がモジュールの契約で、ファイルの他の部分はモジュールから見えません。
89
+ - **モジュール間の結線はアプリケーションに置く。** モジュールは自分が読むキー(`auth.db`)を宣言するだけで、アプリケーションが与えるべきものにはデフォルト値を置きません。アプリケーションがそれを共有の値に結びつけます(`auth.db = ${shared.db}`)。モジュールは互いのキーもアプリケーションのキーも参照しないので、依存は一方向に流れます。
90
+
91
+ ```hocon
92
+ # auth/reference.conf: auth パッケージに同梱され、auth チームが持つ
93
+ auth {
94
+ session { lifetime = 30m, max-body = 64KiB }
95
+ # db: デフォルトなし。auth.db はアプリケーションが与える
96
+ }
97
+ ```
98
+
99
+ ```hocon
100
+ # application.conf: アプリケーションが持つ
101
+ include package("github.com/acme/auth", "reference.conf")
102
+
103
+ shared.db { host = db.internal, port = 5432 }
104
+ auth.db = ${shared.db} # 結線はアプリケーションに置く
105
+ auth.session.lifetime = 2h # 違うところだけ上書きする。残りはモジュールのデフォルトのまま
106
+ ```
107
+
108
+ ```ts
109
+ // auth モジュールの中
110
+ import { getValidated, hocon } from '@o3co/ts.hocon/zod'
111
+ import { z } from 'zod'
112
+
113
+ const Auth = z.object({
114
+ session: z.object({
115
+ lifetime: hocon.duration('s').pipe(z.number().int().positive()),
116
+ 'max-body': hocon.bytes().pipe(z.number().int().positive()),
117
+ }),
118
+ db: z.object({ host: z.string(), port: z.number().int() }),
119
+ })
120
+ const auth = getValidated(config, 'auth', Auth)
121
+ ```
122
+
123
+ `include package(...)` は HOCON.md の一部ではなく、o3co の各実装が共有する規約([E11](https://github.com/o3co/xx.hocon/blob/main/docs/extra-spec-conventions.md#e11))で、`include classpath(...)` の JVM 以外での対応物です。モジュールは、アプリケーションがファイルの置き場所を知らなくても済む形でデフォルト値を公開できます(ts.hocon の既定のリゾルバーは `<identifier>/<file>` を include 元のファイルから Node のモジュール解決に渡すので、identifier には `@acme/auth` のような npm のパッケージ名を書きます。TS だけで使う設定ではこれも E11 で認められています。上の例のような言語横断の Go 形式の identifier を使うには、それをパッケージに対応づける `packageResolver` が必要です。[`ParseOptions`](#パース関数) を参照)。JVM では Lightbend の `reference.conf` 規約が同じ役割を果たします。
124
+
75
125
  ## 特徴
76
126
 
77
127
  - 完全な HOCON パース:オブジェクト、配列、スカラー値、代入(`${path}`、`${?path}`)
@@ -80,14 +130,17 @@ HOCON は単なるシリアライズ形式ではなく、**プログラムに注
80
130
  - `+=` 追加演算子
81
131
  - `include "file.conf"` および `include file("file.conf")` ディレクティブ
82
132
  - トリプルクォート文字列(`"""..."""`)
133
+ - duration・byte size・period の読み取り(`getDuration()`、`getBytes()`、`getPeriod()`)
83
134
  - HOCON エミッタ(`renderHocon()`)— 解決済み `Config` を HOCON テキストとして出力。再パースすると同一の値ツリーになる(E18 ラウンドトリップ契約)
84
135
  - 同期・非同期 API(`parse` / `parseAsync` / `parseFile` / `parseFileAsync`)
85
136
  - ESM + CJS デュアルパッケージ(全エントリポイントを CI で両形式ロード検証)
86
137
  - [Zod](https://zod.dev/) スキーマバリデーション統合(オプション)
87
- - ブラウザ対応(`parse`/`parseAsync` — Node.js 不要)
138
+ - ブラウザで動作(`parse`/`parseAsync`。bundler で Node の組み込みモジュールを stub する必要あり。[ブラウザ対応](#ブラウザ対応)を参照)
88
139
 
89
140
  ## API
90
141
 
142
+ API の詳細は [o3co.github.io/ts.hocon](https://o3co.github.io/ts.hocon/) を参照してください(TypeDoc で生成し、マイナー / メジャーリリースごとに更新)。
143
+
91
144
  ### パース関数
92
145
 
93
146
  ```ts
@@ -107,22 +160,74 @@ parseFileAsync(path: string, opts?: ParseOptions): Promise<Config>
107
160
  | `env` | `Record<string, string>` | 代入で使用する環境変数(デフォルト: `process.env`) |
108
161
  | `readFileSync` | `(path: string) => string` | カスタムファイルリーダー(同期) |
109
162
  | `readFile` | `(path: string) => Promise<string>` | カスタムファイルリーダー(非同期) |
163
+ | `resolveSubstitutions` | `boolean` | `false` にすると未解決の Config を返し、後で解決できる(デフォルト `true`。[遅延解決](#遅延解決-apie12)を参照) |
164
+ | `originDescription` | `string` | ファイルパスがないときに解決エラーのメッセージで使うソース名 |
165
+ | `resolveFrom` | `string \| string[]` | `include package(...)` で Node のモジュール解決を始める場所(デフォルト: `baseDir`。`parseFile` ではファイルのディレクトリ。なければ `process.cwd()`) |
166
+ | `packageResolver` | `PackageResolver` | `include package(...)` 用の独自リゾルバー(Yarn PnP、bundler、エッジランタイム、テスト)。指定すると `resolveFrom` は無視される |
110
167
 
111
168
  ### Config メソッド
112
169
 
113
170
  | メソッド | 戻り値 | スローする条件 |
114
171
  |---------|--------|--------------|
115
172
  | `get(path)` | `unknown \| undefined` | — |
116
- | `getString(path)` | `string` | 存在しない、型が違う |
117
- | `getNumber(path)` | `number` | 存在しない、型が違う |
118
- | `getBoolean(path)` | `boolean` | 存在しない、型が違う |
119
- | `getConfig(path)` | `Config` | 存在しない、オブジェクトでない |
120
- | `getList(path)` | `unknown[]` | 存在しない、配列でない |
173
+ | `getValue(path)`(値ツリーをそのまま返す。構造の確認用) | `ReadonlyHoconValue \| undefined` | 未解決 |
174
+ | `getString(path)` | `string` | 存在しない、型が違う、未解決 |
175
+ | `getNumber(path)` | `number` | 存在しない、型が違う、未解決 |
176
+ | `getBoolean(path)` | `boolean` | 存在しない、型が違う、未解決 |
177
+ | `getConfig(path)` | `Config` | 存在しない、オブジェクトでない、未解決 |
178
+ | `getList(path)` | `unknown[]` | 存在しない、配列でない、未解決 |
179
+ | `getDuration(path, unit?)` | `number` | 存在しない、文字列でも数値でもない、duration として不正、有限の数値にならない(E21) |
180
+ | `getBytes(path, unit?)` | `number` | 存在しない、文字列でも数値でもない、byte size として不正、負の値、2^53-1 バイトを超える |
181
+ | `getPeriod(path)` | `Period` | 存在しない、文字列でも数値でもない、period として不正 |
121
182
  | `has(path)` | `boolean` | — |
122
183
  | `keys()` | `string[]` | — |
123
184
  | `withFallback(fallback)` | `Config` | — |
124
- | `toObject()` | `unknown` | — |
125
- | `renderHocon()` | `string` | 未解決の場合 |
185
+ | `resolve(opts?)` | `Config` | 解決できない代入(`allowUnresolved: true` の場合を除く) |
186
+ | `resolveWith(source, opts?)` | `Config` | source が未解決、解決できない代入 |
187
+ | `isResolved()` | `boolean` | — |
188
+ | `toObject(opts?)` | `unknown` | —(`{ nullPrototype: true }` で何も継承しないオブジェクトを返す。[セキュリティ上の考慮事項](#セキュリティ上の考慮事項)を参照) |
189
+ | `renderHocon()` | `string` | 未解決 |
190
+
191
+ 上の「スローする条件」はすべて `.path` を持つ `ConfigError` です。ただし次の 2 つは別のクラスです。`getValue` や getter で未解決の値を読んだとき、および `resolveWith` の source が未解決のときは `NotResolvedError`(`ConfigError` のサブクラス)を投げます。`resolve` / `resolveWith` で解決できない代入は `ResolveError` です。
192
+
193
+ ### 遅延解決 API(E12)
194
+
195
+ パース、フォールバックの重ね合わせ、解決を別々の手順に分け、実行時に設定を注入できます。
196
+
197
+ ```ts
198
+ import { parseStringWithOptions, fromMap, empty } from '@o3co/ts.hocon'
199
+ import type { ResolveOptions } from '@o3co/ts.hocon'
200
+
201
+ // 1. 解決せずにパース — 代入は保留される
202
+ const cfg = parseStringWithOptions(
203
+ 'version = ${shortversion}-${CI_RUN_NUMBER}\nvariables { shortversion = "1.2.3" }',
204
+ { resolveSubstitutions: false }
205
+ )
206
+ cfg.isResolved() // false — ${CI_RUN_NUMBER} がまだ保留中
207
+
208
+ // 2. 実行時のフォールバックを重ねる
209
+ const runtime = fromMap({ CI_RUN_NUMBER: '42' })
210
+ const vars = cfg.getConfig('variables') // 代入ではないので、もう取得できる
211
+ const merged = cfg.withFallback(runtime).withFallback(vars)
212
+
213
+ // 3. フォールバックの重なり全体を解決する
214
+ const resolved = merged.resolve({ useSystemEnvironment: false })
215
+ resolved.getString('version') // "1.2.3-42"
216
+
217
+ // resolveWith: source を参照用に使って receiver を解決する。source のキーは結果に入らない
218
+ const receiver = parseStringWithOptions('r = ${key}', { resolveSubstitutions: false })
219
+ const source = fromMap({ key: 'val' })
220
+ const result = receiver.resolveWith(source)
221
+ result.has('key') // false — source のキーは含まれない
222
+ result.getString('r') // "val"
223
+ ```
224
+
225
+ `ResolveOptions`:
226
+
227
+ | オプション | 型 | デフォルト | 説明 |
228
+ |--------|------|---------|-------------|
229
+ | `useSystemEnvironment` | `boolean` | `true` | 代入のフォールバックとして環境変数(`ParseOptions.env`、デフォルトは `process.env`)を参照する |
230
+ | `allowUnresolved` | `boolean` | `false` | 解決できない代入をそのまま残す(エラーにしない) |
126
231
 
127
232
  ### Zod 統合
128
233
 
@@ -144,21 +249,58 @@ const app = validate(cfg, Schema)
144
249
  const port = getValidated(cfg, 'server.port', z.number().int())
145
250
  ```
146
251
 
252
+ スキーマが数値や真偽値を求める位置では文字列を変換するので、`PORT=8081` のとき `port = ${?PORT}` は `z.number()` を通ります。変換はスキーマに沿って、object・array・record(各値)・tuple・union・pipe(入力側のスキーマ)・lazy と、optional / nullable / default / prefault / nonoptional / catch / readonly の各ラッパーの中まで届きます。数値や真偽値の literal にも変換します(`z.literal(1)` に対する `"1"`)。object の `catchall` と intersection の中には届きません。`z.discriminatedUnion` は discriminator が選ぶ option に従って変換します。それ以外の union は、値をそのまま受け入れる option があれば変換せず(`z.union([z.number(), z.string()])` では `"5"` は文字列のまま)、なければ変換後の値を受け入れる最初の option に従って変換します(`z.union([z.number(), z.literal('auto')])` では `"8081"` は 8081)。その option を探すために option を順に試すので、深く再帰する通常の union は検証が遅くなります。そこでは `z.discriminatedUnion` を使ってください。
253
+
254
+ HOCON の duration と byte size は文字列(`30m`、`64KiB`)なので、`z.number()` では通りません。`hocon.duration(unit?)` / `hocon.bytes(unit?)` マーカーは `getDuration()` / `getBytes()` と同じ規則で読み、指定した単位の数値を返します(既定は `ms` / `B`。単位のない数値は getter と同じ意味)。範囲や整数のチェックは、マーカーを数値スキーマに pipe して書きます:
255
+
256
+ ```ts
257
+ import { hocon, getValidated } from '@o3co/ts.hocon/zod'
258
+
259
+ const Session = z.object({
260
+ lifetime: hocon.duration('s').pipe(z.number().int().min(60).max(86_400)),
261
+ maxBody: hocon.bytes().pipe(z.number().int().positive()),
262
+ })
263
+
264
+ // session { lifetime = 30m, maxBody = 64KiB }
265
+ getValidated(cfg, 'session', Session) // { lifetime: 1800, maxBody: 65536 }
266
+ ```
267
+
268
+ `hocon.period()` は `getPeriod()` と同じ規則で period を読み、`Period`(`{ years, months, days }`)を返します。制約はスキーマ側で refine します:
269
+
270
+ ```ts
271
+ const Backup = z.object({
272
+ keep: hocon.period().refine((p) => p.years === 0, 'use months or days'),
273
+ })
274
+
275
+ // backup { keep = 2w }
276
+ getValidated(cfg, 'backup', Backup) // { keep: { years: 0, months: 0, days: 14 } }
277
+ ```
278
+
279
+ 不正な duration / byte size / period は、キーのパス付きのバリデーションエラーになります。issue の message は問題だけを示し、値は含めません(`not a duration`、`byte size must be non-negative`)。誤ったキーに置かれたシークレットがログに出ないようにするためです。`params.problem` は `invalid`・`negative`・`overflow` のいずれかで、値は Zod に input の報告を求めたときだけ issue の `input` に入ります。マーカーは普通の Zod スキーマなので、`validate`・`getValidated`・`parseWithSchema`・`parseFileWithSchema` をはじめ、Zod が実行するところならどこでも使えます。指数表記(`1e3`、`"1.5e2s"`)は getter もマーカーも読みます。ただしマーカーは数値を JavaScript の値として読むため、double の精度を超える桁数の数値リテラルは、リテラルの文字列を正確に読む getter と結果が異なることがあります(`1.99999999999999999` B は `getBytes()` では 1、マーカーでは 2)。period のマーカーは、小数点や指数を含むリテラル(`7.0`、`1e2`)も整数として読みます(`getPeriod()` は拒否します)。単位付きで書けば(`30s`、`7d`)どちらの差も出ません。
280
+
281
+ 未解決の Config(`resolveSubstitutions: false` でパースしたもの、`resolve()` 前のフォールバックの重なり、または何かが残った `resolve({ allowUnresolved: true })` の結果)に対しては、`validate` は保留中の値が欠けたツリーを検証するのではなく、getter と同じく `NotResolvedError` を投げます。`getValidated` は、値が未解決のパスでは `NotResolvedError` を投げ、解決済みのパスなら検証します。先に `resolve()` を呼んでください。
282
+
147
283
  Zod をピア依存としてインストール:
148
284
  ```bash
149
285
  npm install zod
150
286
  ```
151
287
 
288
+ Zod の入口(`validate`・`getValidated`・`parseWithSchema`・`parseFileWithSchema`)は、値のどこかに `__proto__`・`constructor`・`prototype` という名前のキーがあると拒否します。Zod は record から `__proto__` キーをエラーなしで落とすため、また結果がスキーマによって変わらないように、3 つとも同じく拒否します。`ZodError` にはキーごとに 1 つの issue が入ります。issue はそのキーを持つオブジェクトの位置に付き、キー名は message と `params.key` に入ります(`key "__proto__" names an object's prototype → at limits`)。その後に、拒否したキーを除いた値に対するスキーマの issue が続くので、1 回の実行で問題がすべてわかります。キー名を変えるか、影響を受けない通常の getter(`cfg.getNumber('limits."constructor"')`)で読んでください。
289
+
152
290
  ### エラー型
153
291
 
154
292
  ```ts
155
- import { ParseError, ResolveError, ConfigError } from '@o3co/ts.hocon'
293
+ import { ParseError, ResolveError, PackageLookupError, ConfigError, NotResolvedError } from '@o3co/ts.hocon'
156
294
 
157
- // ParseError — 字句解析・構文解析エラー: .line, .col, .file?
158
- // ResolveError — 代入・include 解決エラー: .path, .line, .col, .file?
159
- // ConfigError — 型不一致・パス不存在エラー: .path
295
+ // ParseError — 字句解析・構文解析エラー: .line, .col, .file?
296
+ // ResolveError — 代入・include 解決エラー: .path, .line, .col, .file?
297
+ // PackageLookupError — include package(...) が見つからない(ResolveError のサブクラス): .identifier, .packageFile
298
+ // ConfigError — 型不一致・パス不存在・不正な値: .path
299
+ // NotResolvedError — 未解決の値を getter や Zod の入口で読んだ(ConfigError のサブクラス): .path
160
300
  ```
161
301
 
302
+ どのエントリポイント(`/zod`、`/adapters/*`)もこれらと同じクラスを投げるので、同じモジュールシステムの中であれば、root の export に対する `instanceof` がエントリポイントをまたいで効きます。Zod の入口は、値がスキーマに合わないとき `ZodError` を投げます。
303
+
162
304
  ## HOCON の例
163
305
 
164
306
  ```hocon
@@ -193,20 +335,86 @@ description = """
193
335
  """
194
336
  ```
195
337
 
338
+ ### duration と byte size
339
+
340
+ ```ts
341
+ const c = parse(`
342
+ timeout = "30s"
343
+ cache-ttl = "5m"
344
+ max-size = "512MiB"
345
+ `)
346
+
347
+ c.getDuration('timeout') // 30000 (ms)
348
+ c.getDuration('timeout', 's') // 30
349
+ c.getDuration('cache-ttl', 'm') // 5
350
+
351
+ c.getBytes('max-size') // 536870912 (bytes)
352
+ c.getBytes('max-size', 'MiB') // 512
353
+ ```
354
+
355
+ duration の単位: `ns`、`us`、`ms`、`s`、`m`、`h`、`d`(`seconds`、`minutes` などの長い形も可)。duration の単位名は大文字小文字を区別し、小文字のみです(HOCON 仕様)。単位のない数値や文字列は、duration ではミリ秒、byte size ではバイトとして読みます。
356
+
357
+ 数値は文字列から正確な 10 進数として読み、単位との積も正確に計算します(E20)。duration はそのあと指定の単位で 1 回だけ丸めます。`1.001s` は `1001` ms(`1001000000` ns)です(Lightbend の double の計算では `1000999999` ns になります)。byte size は、変換の前に 0 方向へ切り捨てて整数バイトにします(Lightbend と同じ)。`1.0009kB` は `1000` です。値が有限の数値にならない duration はエラーです(E21)。byte size は 0 以上かつ 2^53-1 バイト(JS の number が正確に表せる最大の整数)以下でなければならないので、exa・zetta・yotta の単位(`EB`、`Ei`/`EiB`、`E`/`e`、`exabyte` など、`Y` まで)を 1 以上の量で書くとエラーになります。小数(`0.001EiB`)か、もっと小さい単位を使ってください。
358
+
359
+ byte size の単位は Lightbend と同じ集合で、同じく大文字小文字を区別します:
360
+
361
+ - バイト: `B`、`b`、`byte`、`bytes`
362
+ - 1000 のべき: `kB`、`MB`、`GB`、`TB`、`PB`、`EB`、`ZB`、`YB`、および `kilobyte(s)` … `yottabyte(s)`
363
+ - 1024 のべき: `Ki`/`KiB`、`Mi`/`MiB`、… `Yi`/`YiB`、および `kibibyte(s)` … `yobibyte(s)`
364
+ - 1 文字(1024 のべき、大文字小文字どちらも可。java `-Xmx` 形式): `K`/`k`、`M`/`m`、`G`/`g`、`T`/`t`、`P`/`p`、`E`/`e`、`Z`/`z`、`Y`/`y`
365
+
366
+ それ以外の綴りはエラーです: `KB`、`kb`、`kib`、`Kilobytes`、`MEGABYTES`。(`getBytes(path, unit)` に渡す `'KB'` などは出力単位で、別の一覧です。)
367
+
368
+ ### period
369
+
370
+ ```ts
371
+ const c = parse(`
372
+ retention = 2w
373
+ billing = "1 month"
374
+ archive = 1y
375
+ grace = 3 // 単位のない整数は日数
376
+ `)
377
+
378
+ c.getPeriod('retention') // { years: 0, months: 0, days: 14 }
379
+ c.getPeriod('billing') // { years: 0, months: 1, days: 0 }
380
+ c.getPeriod('archive') // { years: 1, months: 0, days: 0 }
381
+ c.getPeriod('grace') // { years: 0, months: 0, days: 3 }
382
+ ```
383
+
384
+ period の単位は `d`/`day`/`days`、`w`/`week`/`weeks`(日数に畳み込み)、`m`/`mo`/`month`/`months`、`y`/`year`/`years` です。単位は小文字のみで、大文字小文字を区別します(ここでの `m` は分ではなく月)。period は整数のみ(`7.5` はエラー。duration や byte size とは違います)で、負の値も読めます。各成分は 32 bit 符号付き整数に収まる必要があります(Lightbend の `java.time.Period`、rs.hocon、py.hocon と同じ)。
385
+
196
386
  ## 仕様準拠
197
387
 
198
388
  [Lightbend HOCON 仕様](https://github.com/lightbend/config/blob/main/HOCON.md) への準拠状況は [`docs/spec-compliance.md`](docs/spec-compliance.md) に項目単位で記載しており、以下の表はそこから計算されます — `tests/docs.test.ts` が再計算し、表がずれるとビルドが fail します。実装横断のロールアップは [`xx.hocon/docs/compliance-matrix.md`](https://github.com/o3co/xx.hocon/blob/main/docs/compliance-matrix.md) を参照してください。
199
389
 
200
390
  | 指標 | 状況 |
201
391
  | -------------------------------------- | ------------- |
202
- | 仕様全体(out-of-scope を含む) | **90.0%** |
392
+ | 仕様全体(out-of-scope を含む) | **91.9%** |
203
393
  | In-scope のみ | **100.0%** |
204
394
  | Lightbend `test01`–`test13` テスト群 | 13/13 合格 |
205
395
 
396
+ **仕様外の規約(E シリーズ)の実装状況:**
397
+
398
+ | 項目 | 内容 | 状況 |
399
+ |------|------|------|
400
+ | E11 | `include package(...)` によるサービスロケーター型の include | ✅ v1.4.0 |
401
+ | E12 | 遅延解決(`parse → withFallback → resolve()` のライフサイクル) | ✅ v1.4.0 |
402
+ | E18 | HOCON エミッタのラウンドトリップ契約(`renderHocon()`) | ✅ v1.13.0 |
403
+ | E19 | double の範囲を超える浮動小数点リテラル(`1e999`)はパースエラー | ✅ v1.14.0 |
404
+ | E20 | 単位値の計算は正確な 10 進数(duration は Lightbend からの意図的な乖離) | ✅ v1.14.0 |
405
+ | E21 | 実装が表現できない duration はエラー | ✅ v1.14.0 |
406
+
407
+ E シリーズは、HOCON.md の外で o3co の各実装が共有する規約です。[`xx.hocon/docs/extra-spec-conventions.md`](https://github.com/o3co/xx.hocon/blob/main/docs/extra-spec-conventions.md) を参照してください。
408
+
206
409
  未対応の機能:
410
+
207
411
  - `include url(...)`
208
412
  - `include classpath(...)`
209
413
 
414
+ v1.0.0 から対応(P1):
415
+
416
+ - `.properties` ファイルの読み込み
417
+
210
418
  ## パフォーマンス
211
419
 
212
420
  ### ts.hocon のパースコスト
@@ -239,26 +447,33 @@ JSON.parse は V8 のネイティブ C++ 実装であり、最速の基準線で
239
447
 
240
448
  ### node-config との機能比較
241
449
 
242
- ts.hocon は [node-config](https://github.com/node-config/node-config)(JSON)と比較して、大幅に豊富な設定機能を提供します:
450
+ JSON の設定ファイルを読む [node-config](https://github.com/node-config/node-config) **v5.0.1** との機能比較(2026-10-16 時点):
243
451
 
244
452
  | 機能 | ts.hocon | node-config (JSON) |
245
453
  |---|---|---|
246
- | コメント | `//` `#` | 非対応 |
454
+ | コメント | `//` `#` | 対応(`//`、`/* */`。`.json` を JSON5 として解析) |
247
455
  | 複数行文字列 | `"""..."""` | 非対応 |
248
456
  | substitution(`${path}`) | 対応 | 非対応 |
249
457
  | optional substitution(`${?path}`) | 対応 | 非対応 |
250
458
  | 環境変数参照 | 対応(substitution経由) | 部分対応(`custom-environment-variables` ファイル) |
251
459
  | include | 対応 | 非対応 |
252
- | ディープマージ | 対応(配列も対応) | 部分対応(配列は置換) |
460
+ | ディープマージ | 対応(オブジェクト。配列は置換、`+=` や連結で追加) | 対応(オブジェクト。配列は置換) |
253
461
  | 追加演算子(`+=`) | 対応 | 非対応 |
254
462
  | 環境別設定 | HOCON仕様で自由に構成可 | 対応(ファイル名規約) |
255
463
  | スキーマ検証 | Zod 統合 | 非対応 |
256
464
  | プログラマティック API | `parse(string)` | ファイルベース初期化後に `get()` |
257
- | 型付きゲッター | `getString`, `getNumber` 等 | `get()`(any) |
465
+ | 型付きゲッター | `getString`, `getNumber` 等(実行時に検査) | `get<T>()`(型パラメータのみ、実行時の検査なし) |
258
466
 
259
467
  ## ブラウザ対応
260
468
 
261
- `parse()` と `parseAsync()` はブラウザで動作します。`parseFile()` と `parseFileAsync()` は Node.js(またはカスタムの `readFileSync`/`readFile` オプション)が必要です。
469
+ `parse()` と `parseAsync()` はブラウザで動作します。adapter と Zod 連携も同じです。`parseFile()` と `parseFileAsync()` は Node.js(またはカスタムの `readFileSync`/`readFile` オプション)が必要です。
470
+
471
+ パッケージは Node の組み込みモジュール `fs`・`path`・`module`・`url` を import しています(ファイルの読み込みと `include package(...)` のため)。使うのはその経路だけです。ブラウザ向けの bundle では、これらを空のモジュールに置き換える必要があります:
472
+
473
+ - **Vite**(7 と 8)は既定でそうするので、設定は不要です。
474
+ - **その他の bundler**: 4 つを空のモジュール(`export default {}`)に割り当ててください。たとえば esbuild の plugin や、webpack 5 の `resolve.fallback: { fs: false, path: false, module: false, url: false }` を使います。external にするだけでは足りません。ブラウザは `fs` を読み込めないからです。
475
+
476
+ ここまでで動くのは、ファイルの include を使わない解析です。独自の `readFile` / `readFileSync`(下記)で include を読む場合は path も解決するので、`path` には空のモジュールではなく、`path-browserify` パッケージのようなブラウザ向けの実装を割り当ててください。`include package(...)` には独自の `packageResolver` が必要です(`ParseOptions` を参照)。
262
477
 
263
478
  ```ts
264
479
  // カスタムファイルローダーを使ったブラウザ利用
@@ -324,10 +539,11 @@ const config = parseWithSchema(hoconInput, schema) // 起動時に即座に失
324
539
  | プロジェクト | 言語 | レジストリ | 説明 |
325
540
  |---------|----------|----------|-------------|
326
541
  | [go.hocon](https://github.com/o3co/go.hocon) | Go | [pkg.go.dev](https://pkg.go.dev/github.com/o3co/go.hocon) | Go 向け HOCON パーサー |
327
- | [rs.hocon](https://github.com/o3co/rs.hocon) | Rust | [crates.io](https://crates.io/crates/o3co-hocon) | Rust 向け HOCON パーサー |
542
+ | [rs.hocon](https://github.com/o3co/rs.hocon) | Rust | [crates.io](https://crates.io/crates/hocon-parser) | Rust 向け HOCON パーサー |
543
+ | [py.hocon](https://github.com/o3co/py.hocon) | Python | [PyPI](https://pypi.org/project/hocon-parser/) | Python 向け HOCON パーサー |
328
544
  | [hocon2](https://github.com/o3co/hocon2) | Go | [pkg.go.dev](https://pkg.go.dev/github.com/o3co/hocon2) | HOCON → JSON/YAML/TOML/Properties 変換 CLI |
329
545
 
330
- 3 つのパーサー実装([ts.hocon](https://github.com/o3co/ts.hocon)、[rs.hocon](https://github.com/o3co/rs.hocon)、[go.hocon](https://github.com/o3co/go.hocon))はすべて同じ Lightbend HOCON 仕様で追跡されています — 実装ごとの準拠率は [横断ロールアップ](https://github.com/o3co/xx.hocon/blob/main/docs/compliance-matrix.md) を参照してください。
546
+ 4 つのパーサー実装([ts.hocon](https://github.com/o3co/ts.hocon)、[rs.hocon](https://github.com/o3co/rs.hocon)、[go.hocon](https://github.com/o3co/go.hocon)、[py.hocon](https://github.com/o3co/py.hocon))はすべて同じ Lightbend HOCON 仕様で追跡されています — 実装ごとの準拠率は [横断ロールアップ](https://github.com/o3co/xx.hocon/blob/main/docs/compliance-matrix.md) を参照してください。
331
547
 
332
548
  ## フォーマットアダプタ
333
549
 
@@ -352,8 +568,8 @@ const merged = cfg.withFallback(base).resolve()
352
568
  | `@o3co/ts.hocon/adapters/env` | — | プレフィックス付き名前空間の一括マウント。`.env` の読み込みも可能 |
353
569
  | `@o3co/ts.hocon/adapters/jsonc` | — | コメントと末尾カンマを許す JSON |
354
570
  | `@o3co/ts.hocon/adapters/json5` | — | JSON5 1.0.0。手書き scanner で依存ゼロ |
355
- | `@o3co/ts.hocon/adapters/toml` | `smol-toml` | オプショナルなピア依存 |
356
- | `@o3co/ts.hocon/adapters/yaml` | `yaml` 2.9.x | オプショナルなピア依存。スカラー解決はそのライブラリの答えで、`version: '1.2'` を明示してドリフトを防止 |
571
+ | `@o3co/ts.hocon/adapters/toml` | `smol-toml`(>= 1.0。1.9.0 以上を推奨、[SECURITY.md](SECURITY.md#the-toml-adapter) 参照) | オプショナルなピア依存 |
572
+ | `@o3co/ts.hocon/adapters/yaml` | `yaml`(>= 2.0) | オプショナルなピア依存。スカラー解決はそのライブラリの答えで、`version: '1.2'` を明示してドリフトを防止 |
357
573
 
358
574
  TOML と YAML のライブラリは **オプショナルなピア依存** なので、本パッケージを入れても
359
575
  何も追加でインストールされません — 実際に使うものだけを足してください。プレーンな JSON に
@@ -374,11 +590,11 @@ cfg.getString('"foo.bar"') // "dotted" — 名前にドットを含む 1 つ
374
590
  cfg.getString('foo.bar') // "nested" — foo -> bar
375
591
  ```
376
592
 
377
- ### JSONC / YAML の数値
593
+ ### JSONC / JSON5 / YAML の数値
378
594
 
379
595
  整数は精度を落とさずに取り込まれます。JS の `number` で表せない幅の整数リテラルも
380
- 桁がそのまま保持され、int64 の範囲を超える値は黙って壊れる代わりにエラーになります。
381
- 取得結果はゲッターによって変わります:
596
+ 桁がそのまま保持され、int64 の範囲を超える値は黙って壊れる代わりにエラーになります
597
+ (JSON5 の 16 進整数も同じです)。取得結果はゲッターによって変わります:
382
598
 
383
599
  ```ts
384
600
  const cfg = parseJsonc('{"id": 9007199254740993}')
@@ -410,6 +626,14 @@ YAML のスカラー解決はそれ以外の点では `yaml` ライブラリの
410
626
  - **include のパストラバーサル:** 相対 `include` パスは `baseDir` から解決され、`..` セグメントで外に出て `/etc/passwd` のような機密ファイルに到達し得ます。信頼できない入力をパースするなら、パスを検証するカスタム `readFileSync`/`readFile` を渡してください。
411
627
  - **入力サイズ:** パーサーに入力サイズ上限はありません。信頼できない入力は `parse()` の前にサイズを検証してください。
412
628
  - **include の深さ:** 深い include 連鎖によるスタックオーバーフローを防ぐため 50 段に制限されています。
629
+ - **写像されたパスの深さ:** 環境変数の `__` セグメントと `.properties` のドット区切りキーは
630
+ 64 セグメントまでです。1 つの名前が任意の深さの連鎖を 1 本作るため、上限がなければ長い
631
+ 変数名 1 つでスタックを使い切れてしまいました。rs.hocon と py.hocon も同じ写像を 64 で
632
+ 制限しています。
633
+ - **ドキュメントのネストの深さ:** 上限はありません。エンジンのスタックを超えるほど深い
634
+ ドキュメントは `ParseError` か `ConfigError` で失敗します(素の `RangeError` にはなりません)。
635
+ ただし、どの深さで失敗するかは呼び出し側のコードがすでにどれだけ深いかに左右されるので、
636
+ 決まった段数に頼らず、信頼できない入力はパース前にサイズを検証してください(上記)。
413
637
  - **プロトタイプ汚染:** `__proto__` / `constructor` / `prototype` という名前のキーは
414
638
  **通常のキーとして保持されます**。`.properties` ファイルや環境変数がそれらを正当に
415
639
  使っていることがあり、落とせば黙ったデータ欠落になるからです。ライブラリ内部の安全性は