@o3co/ts.hocon 1.12.0 → 1.14.0
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/README.ja.md +256 -32
- package/README.md +166 -32
- package/dist/adapters/env.cjs +26 -1678
- package/dist/adapters/env.d.cts +1 -1
- package/dist/adapters/env.d.ts +1 -1
- package/dist/adapters/env.js +11 -1625
- package/dist/adapters/json5.cjs +496 -0
- package/dist/adapters/json5.d.cts +47 -0
- package/dist/adapters/json5.d.ts +47 -0
- package/dist/adapters/json5.js +496 -0
- package/dist/adapters/jsonc.cjs +17 -1632
- package/dist/adapters/jsonc.d.cts +1 -1
- package/dist/adapters/jsonc.d.ts +1 -1
- package/dist/adapters/jsonc.js +9 -1586
- package/dist/adapters/properties.cjs +7 -1773
- package/dist/adapters/properties.d.cts +1 -1
- package/dist/adapters/properties.d.ts +1 -1
- package/dist/adapters/properties.js +6 -1735
- package/dist/adapters/toml.cjs +12 -1626
- package/dist/adapters/toml.d.cts +1 -1
- package/dist/adapters/toml.d.ts +1 -1
- package/dist/adapters/toml.js +10 -1589
- package/dist/adapters/yaml.cjs +20 -1635
- package/dist/adapters/yaml.d.cts +1 -1
- package/dist/adapters/yaml.d.ts +1 -1
- package/dist/adapters/yaml.js +10 -1589
- package/dist/chunk-GBNQADHT.cjs +83 -0
- package/dist/chunk-IMJ7QHIK.cjs +158 -0
- package/dist/chunk-QY7ECGS6.js +83 -0
- package/dist/chunk-RO4D6BH7.cjs +3792 -0
- package/dist/chunk-WTXULUZS.js +3792 -0
- package/dist/chunk-XWKO4JM3.js +158 -0
- package/dist/{config-Dlq6rF3w.d.cts → config-OccaKqXu.d.cts} +148 -4
- package/dist/{config-Dlq6rF3w.d.ts → config-OccaKqXu.d.ts} +148 -4
- package/dist/index.cjs +35 -3449
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +25 -3383
- package/dist/zod.cjs +107 -3291
- package/dist/zod.d.cts +83 -3
- package/dist/zod.d.ts +83 -3
- package/dist/zod.js +95 -3245
- package/package.json +12 -2
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 ごと・リリースごとに、ビルド成果物に対して
|
|
36
|
-
ロードし、実際に呼び出し(バンドル形式で挙動が変わる package include
|
|
37
|
-
|
|
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,13 +130,17 @@ HOCON は単なるシリアライズ形式ではなく、**プログラムに注
|
|
|
80
130
|
- `+=` 追加演算子
|
|
81
131
|
- `include "file.conf"` および `include file("file.conf")` ディレクティブ
|
|
82
132
|
- トリプルクォート文字列(`"""..."""`)
|
|
133
|
+
- duration・byte size・period の読み取り(`getDuration()`、`getBytes()`、`getPeriod()`)
|
|
134
|
+
- HOCON エミッタ(`renderHocon()`)— 解決済み `Config` を HOCON テキストとして出力。再パースすると同一の値ツリーになる(E18 ラウンドトリップ契約)
|
|
83
135
|
- 同期・非同期 API(`parse` / `parseAsync` / `parseFile` / `parseFileAsync`)
|
|
84
136
|
- ESM + CJS デュアルパッケージ(全エントリポイントを CI で両形式ロード検証)
|
|
85
137
|
- [Zod](https://zod.dev/) スキーマバリデーション統合(オプション)
|
|
86
|
-
-
|
|
138
|
+
- ブラウザで動作(`parse`/`parseAsync`。bundler で Node の組み込みモジュールを stub する必要あり。[ブラウザ対応](#ブラウザ対応)を参照)
|
|
87
139
|
|
|
88
140
|
## API
|
|
89
141
|
|
|
142
|
+
API の詳細は [o3co.github.io/ts.hocon](https://o3co.github.io/ts.hocon/) を参照してください(TypeDoc で生成し、マイナー / メジャーリリースごとに更新)。
|
|
143
|
+
|
|
90
144
|
### パース関数
|
|
91
145
|
|
|
92
146
|
```ts
|
|
@@ -106,21 +160,74 @@ parseFileAsync(path: string, opts?: ParseOptions): Promise<Config>
|
|
|
106
160
|
| `env` | `Record<string, string>` | 代入で使用する環境変数(デフォルト: `process.env`) |
|
|
107
161
|
| `readFileSync` | `(path: string) => string` | カスタムファイルリーダー(同期) |
|
|
108
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` は無視される |
|
|
109
167
|
|
|
110
168
|
### Config メソッド
|
|
111
169
|
|
|
112
170
|
| メソッド | 戻り値 | スローする条件 |
|
|
113
171
|
|---------|--------|--------------|
|
|
114
172
|
| `get(path)` | `unknown \| undefined` | — |
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
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 として不正 |
|
|
120
182
|
| `has(path)` | `boolean` | — |
|
|
121
183
|
| `keys()` | `string[]` | — |
|
|
122
184
|
| `withFallback(fallback)` | `Config` | — |
|
|
123
|
-
| `
|
|
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` | 解決できない代入をそのまま残す(エラーにしない) |
|
|
124
231
|
|
|
125
232
|
### Zod 統合
|
|
126
233
|
|
|
@@ -142,21 +249,56 @@ const app = validate(cfg, Schema)
|
|
|
142
249
|
const port = getValidated(cfg, 'server.port', z.number().int())
|
|
143
250
|
```
|
|
144
251
|
|
|
252
|
+
HOCON の duration と byte size は文字列(`30m`、`64KiB`)なので、`z.number()` では通りません。`hocon.duration(unit?)` / `hocon.bytes(unit?)` マーカーは `getDuration()` / `getBytes()` と同じ規則で読み、指定した単位の数値を返します(既定は `ms` / `B`。単位のない数値は getter と同じ意味)。範囲や整数のチェックは、マーカーを数値スキーマに pipe して書きます:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { hocon, getValidated } from '@o3co/ts.hocon/zod'
|
|
256
|
+
|
|
257
|
+
const Session = z.object({
|
|
258
|
+
lifetime: hocon.duration('s').pipe(z.number().int().min(60).max(86_400)),
|
|
259
|
+
maxBody: hocon.bytes().pipe(z.number().int().positive()),
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// session { lifetime = 30m, maxBody = 64KiB }
|
|
263
|
+
getValidated(cfg, 'session', Session) // { lifetime: 1800, maxBody: 65536 }
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`hocon.period()` は `getPeriod()` と同じ規則で period を読み、`Period`(`{ years, months, days }`)を返します。制約はスキーマ側で refine します:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
const Backup = z.object({
|
|
270
|
+
keep: hocon.period().refine((p) => p.years === 0, 'use months or days'),
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// backup { keep = 2w }
|
|
274
|
+
getValidated(cfg, 'backup', Backup) // { keep: { years: 0, months: 0, days: 14 } }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
不正な duration / byte size / period は、キーのパス付きのバリデーションエラーになります。マーカーは普通の 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`)どちらの差も出ません。
|
|
278
|
+
|
|
279
|
+
未解決の Config(`resolveSubstitutions: false` でパースしたもの、`resolve()` 前のフォールバックの重なり、または何かが残った `resolve({ allowUnresolved: true })` の結果)に対しては、`validate` は保留中の値が欠けたツリーを検証するのではなく、getter と同じく `NotResolvedError` を投げます。`getValidated` は、値が未解決のパスでは `NotResolvedError` を投げ、解決済みのパスなら検証します。先に `resolve()` を呼んでください。
|
|
280
|
+
|
|
145
281
|
Zod をピア依存としてインストール:
|
|
146
282
|
```bash
|
|
147
283
|
npm install zod
|
|
148
284
|
```
|
|
149
285
|
|
|
286
|
+
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`)。キー名を変えるか、影響を受けない通常の getter(`cfg.getNumber('limits."constructor"')`)で読んでください。
|
|
287
|
+
|
|
150
288
|
### エラー型
|
|
151
289
|
|
|
152
290
|
```ts
|
|
153
|
-
import { ParseError, ResolveError, ConfigError } from '@o3co/ts.hocon'
|
|
291
|
+
import { ParseError, ResolveError, PackageLookupError, ConfigError, NotResolvedError } from '@o3co/ts.hocon'
|
|
154
292
|
|
|
155
|
-
// ParseError
|
|
156
|
-
// ResolveError
|
|
157
|
-
//
|
|
293
|
+
// ParseError — 字句解析・構文解析エラー: .line, .col, .file?
|
|
294
|
+
// ResolveError — 代入・include 解決エラー: .path, .line, .col, .file?
|
|
295
|
+
// PackageLookupError — include package(...) が見つからない(ResolveError のサブクラス): .identifier, .packageFile
|
|
296
|
+
// ConfigError — 型不一致・パス不存在・不正な値: .path
|
|
297
|
+
// NotResolvedError — 未解決の値を getter や Zod の入口で読んだ(ConfigError のサブクラス): .path
|
|
158
298
|
```
|
|
159
299
|
|
|
300
|
+
どのエントリポイント(`/zod`、`/adapters/*`)もこれらと同じクラスを投げるので、同じモジュールシステムの中であれば、root の export に対する `instanceof` がエントリポイントをまたいで効きます。Zod の入口は、値がスキーマに合わないとき `ZodError` を投げます。
|
|
301
|
+
|
|
160
302
|
## HOCON の例
|
|
161
303
|
|
|
162
304
|
```hocon
|
|
@@ -191,20 +333,85 @@ description = """
|
|
|
191
333
|
"""
|
|
192
334
|
```
|
|
193
335
|
|
|
336
|
+
### duration と byte size
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
const c = parse(`
|
|
340
|
+
timeout = "30s"
|
|
341
|
+
cache-ttl = "5m"
|
|
342
|
+
max-size = "512MiB"
|
|
343
|
+
`)
|
|
344
|
+
|
|
345
|
+
c.getDuration('timeout') // 30000 (ms)
|
|
346
|
+
c.getDuration('timeout', 's') // 30
|
|
347
|
+
c.getDuration('cache-ttl', 'm') // 5
|
|
348
|
+
|
|
349
|
+
c.getBytes('max-size') // 536870912 (bytes)
|
|
350
|
+
c.getBytes('max-size', 'MiB') // 512
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
duration の単位: `ns`、`us`、`ms`、`s`、`m`、`h`、`d`(`seconds`、`minutes` などの長い形も可)。duration の単位名は大文字小文字を区別し、小文字のみです(HOCON 仕様)。単位のない数値や文字列は、duration ではミリ秒、byte size ではバイトとして読みます。
|
|
354
|
+
|
|
355
|
+
数値は文字列から正確な 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`)か、もっと小さい単位を使ってください。
|
|
356
|
+
|
|
357
|
+
byte size の単位は Lightbend と同じ集合で、同じく大文字小文字を区別します:
|
|
358
|
+
|
|
359
|
+
- バイト: `B`、`b`、`byte`、`bytes`
|
|
360
|
+
- 1000 のべき: `kB`、`MB`、`GB`、`TB`、`PB`、`EB`、`ZB`、`YB`、および `kilobyte(s)` … `yottabyte(s)`
|
|
361
|
+
- 1024 のべき: `Ki`/`KiB`、`Mi`/`MiB`、… `Yi`/`YiB`、および `kibibyte(s)` … `yobibyte(s)`
|
|
362
|
+
- 1 文字(1024 のべき、大文字小文字どちらも可。java `-Xmx` 形式): `K`/`k`、`M`/`m`、`G`/`g`、`T`/`t`、`P`/`p`、`E`/`e`、`Z`/`z`、`Y`/`y`
|
|
363
|
+
|
|
364
|
+
それ以外の綴りはエラーです: `KB`、`kb`、`kib`、`Kilobytes`、`MEGABYTES`。(`getBytes(path, unit)` に渡す `'KB'` などは出力単位で、別の一覧です。)
|
|
365
|
+
|
|
366
|
+
### period
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
const c = parse(`
|
|
370
|
+
retention = 2w
|
|
371
|
+
billing = "1 month"
|
|
372
|
+
archive = 1y
|
|
373
|
+
grace = 3 // 単位のない整数は日数
|
|
374
|
+
`)
|
|
375
|
+
|
|
376
|
+
c.getPeriod('retention') // { years: 0, months: 0, days: 14 }
|
|
377
|
+
c.getPeriod('billing') // { years: 0, months: 1, days: 0 }
|
|
378
|
+
c.getPeriod('archive') // { years: 1, months: 0, days: 0 }
|
|
379
|
+
c.getPeriod('grace') // { years: 0, months: 0, days: 3 }
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
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 と同じ)。
|
|
383
|
+
|
|
194
384
|
## 仕様準拠
|
|
195
385
|
|
|
196
|
-
[Lightbend HOCON 仕様](https://github.com/lightbend/config/blob/main/HOCON.md) への準拠状況は [`docs/spec-compliance.md`](docs/spec-compliance.md)
|
|
386
|
+
[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) を参照してください。
|
|
197
387
|
|
|
198
388
|
| 指標 | 状況 |
|
|
199
389
|
| -------------------------------------- | ------------- |
|
|
200
|
-
| 仕様全体(out-of-scope を含む) | **
|
|
201
|
-
| In-scope のみ | **
|
|
390
|
+
| 仕様全体(out-of-scope を含む) | **91.9%** |
|
|
391
|
+
| In-scope のみ | **100.0%** |
|
|
202
392
|
| Lightbend `test01`–`test13` テスト群 | 13/13 合格 |
|
|
203
393
|
|
|
204
|
-
|
|
394
|
+
**仕様外の規約(E シリーズ)の実装状況:**
|
|
395
|
+
|
|
396
|
+
| 項目 | 内容 | 状況 |
|
|
397
|
+
|------|------|------|
|
|
398
|
+
| E11 | `include package(...)` によるサービスロケーター型の include | ✅ v1.4.0 |
|
|
399
|
+
| E12 | 遅延解決(`parse → withFallback → resolve()` のライフサイクル) | ✅ v1.4.0 |
|
|
400
|
+
| E18 | HOCON エミッタのラウンドトリップ契約(`renderHocon()`) | ✅ v1.13.0 |
|
|
401
|
+
| E19 | double の範囲を超える浮動小数点リテラル(`1e999`)はパースエラー | ✅ v1.14.0 |
|
|
402
|
+
| E20 | 単位値の計算は正確な 10 進数(duration は Lightbend からの意図的な乖離) | ✅ v1.14.0 |
|
|
403
|
+
| E21 | 実装が表現できない duration はエラー | ✅ v1.14.0 |
|
|
404
|
+
|
|
405
|
+
E シリーズは、HOCON.md の外で o3co の各実装が共有する規約です。[`xx.hocon/docs/extra-spec-conventions.md`](https://github.com/o3co/xx.hocon/blob/main/docs/extra-spec-conventions.md) を参照してください。
|
|
406
|
+
|
|
407
|
+
未対応の機能:
|
|
408
|
+
|
|
205
409
|
- `include url(...)`
|
|
206
410
|
- `include classpath(...)`
|
|
207
|
-
|
|
411
|
+
|
|
412
|
+
v1.0.0 から対応(P1):
|
|
413
|
+
|
|
414
|
+
- `.properties` ファイルの読み込み
|
|
208
415
|
|
|
209
416
|
## パフォーマンス
|
|
210
417
|
|
|
@@ -238,26 +445,33 @@ JSON.parse は V8 のネイティブ C++ 実装であり、最速の基準線で
|
|
|
238
445
|
|
|
239
446
|
### node-config との機能比較
|
|
240
447
|
|
|
241
|
-
|
|
448
|
+
JSON の設定ファイルを読む [node-config](https://github.com/node-config/node-config) **v5.0.1** との機能比較(2026-10-16 時点):
|
|
242
449
|
|
|
243
450
|
| 機能 | ts.hocon | node-config (JSON) |
|
|
244
451
|
|---|---|---|
|
|
245
|
-
| コメント | `//` `#` |
|
|
452
|
+
| コメント | `//` `#` | 対応(`//`、`/* */`。`.json` を JSON5 として解析) |
|
|
246
453
|
| 複数行文字列 | `"""..."""` | 非対応 |
|
|
247
454
|
| substitution(`${path}`) | 対応 | 非対応 |
|
|
248
455
|
| optional substitution(`${?path}`) | 対応 | 非対応 |
|
|
249
456
|
| 環境変数参照 | 対応(substitution経由) | 部分対応(`custom-environment-variables` ファイル) |
|
|
250
457
|
| include | 対応 | 非対応 |
|
|
251
|
-
| ディープマージ |
|
|
458
|
+
| ディープマージ | 対応(オブジェクト。配列は置換、`+=` や連結で追加) | 対応(オブジェクト。配列は置換) |
|
|
252
459
|
| 追加演算子(`+=`) | 対応 | 非対応 |
|
|
253
460
|
| 環境別設定 | HOCON仕様で自由に構成可 | 対応(ファイル名規約) |
|
|
254
461
|
| スキーマ検証 | Zod 統合 | 非対応 |
|
|
255
462
|
| プログラマティック API | `parse(string)` | ファイルベース初期化後に `get()` |
|
|
256
|
-
| 型付きゲッター | `getString`, `getNumber`
|
|
463
|
+
| 型付きゲッター | `getString`, `getNumber` 等(実行時に検査) | `get<T>()`(型パラメータのみ、実行時の検査なし) |
|
|
257
464
|
|
|
258
465
|
## ブラウザ対応
|
|
259
466
|
|
|
260
|
-
`parse()` と `parseAsync()`
|
|
467
|
+
`parse()` と `parseAsync()` はブラウザで動作します。adapter と Zod 連携も同じです。`parseFile()` と `parseFileAsync()` は Node.js(またはカスタムの `readFileSync`/`readFile` オプション)が必要です。
|
|
468
|
+
|
|
469
|
+
パッケージは Node の組み込みモジュール `fs`・`path`・`module`・`url` を import しています(ファイルの読み込みと `include package(...)` のため)。使うのはその経路だけです。ブラウザ向けの bundle では、これらを空のモジュールに置き換える必要があります:
|
|
470
|
+
|
|
471
|
+
- **Vite**(7 と 8)は既定でそうするので、設定は不要です。
|
|
472
|
+
- **その他の bundler**: 4 つを空のモジュール(`export default {}`)に割り当ててください。たとえば esbuild の plugin や、webpack 5 の `resolve.fallback: { fs: false, path: false, module: false, url: false }` を使います。external にするだけでは足りません。ブラウザは `fs` を読み込めないからです。
|
|
473
|
+
|
|
474
|
+
ここまでで動くのは、ファイルの include を使わない解析です。独自の `readFile` / `readFileSync`(下記)で include を読む場合は path も解決するので、`path` には空のモジュールではなく、`path-browserify` パッケージのようなブラウザ向けの実装を割り当ててください。`include package(...)` には独自の `packageResolver` が必要です(`ParseOptions` を参照)。
|
|
261
475
|
|
|
262
476
|
```ts
|
|
263
477
|
// カスタムファイルローダーを使ったブラウザ利用
|
|
@@ -323,10 +537,11 @@ const config = parseWithSchema(hoconInput, schema) // 起動時に即座に失
|
|
|
323
537
|
| プロジェクト | 言語 | レジストリ | 説明 |
|
|
324
538
|
|---------|----------|----------|-------------|
|
|
325
539
|
| [go.hocon](https://github.com/o3co/go.hocon) | Go | [pkg.go.dev](https://pkg.go.dev/github.com/o3co/go.hocon) | Go 向け HOCON パーサー |
|
|
326
|
-
| [rs.hocon](https://github.com/o3co/rs.hocon) | Rust | [crates.io](https://crates.io/crates/
|
|
540
|
+
| [rs.hocon](https://github.com/o3co/rs.hocon) | Rust | [crates.io](https://crates.io/crates/hocon-parser) | Rust 向け HOCON パーサー |
|
|
541
|
+
| [py.hocon](https://github.com/o3co/py.hocon) | Python | [PyPI](https://pypi.org/project/hocon-parser/) | Python 向け HOCON パーサー |
|
|
327
542
|
| [hocon2](https://github.com/o3co/hocon2) | Go | [pkg.go.dev](https://pkg.go.dev/github.com/o3co/hocon2) | HOCON → JSON/YAML/TOML/Properties 変換 CLI |
|
|
328
543
|
|
|
329
|
-
|
|
544
|
+
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) を参照してください。
|
|
330
545
|
|
|
331
546
|
## フォーマットアダプタ
|
|
332
547
|
|
|
@@ -350,8 +565,9 @@ const merged = cfg.withFallback(base).resolve()
|
|
|
350
565
|
| `@o3co/ts.hocon/adapters/properties` | — | `java.util.Properties`。`include` と構文レイヤーを共有 |
|
|
351
566
|
| `@o3co/ts.hocon/adapters/env` | — | プレフィックス付き名前空間の一括マウント。`.env` の読み込みも可能 |
|
|
352
567
|
| `@o3co/ts.hocon/adapters/jsonc` | — | コメントと末尾カンマを許す JSON |
|
|
353
|
-
| `@o3co/ts.hocon/adapters/
|
|
354
|
-
| `@o3co/ts.hocon/adapters/
|
|
568
|
+
| `@o3co/ts.hocon/adapters/json5` | — | JSON5 1.0.0。手書き scanner で依存ゼロ |
|
|
569
|
+
| `@o3co/ts.hocon/adapters/toml` | `smol-toml`(>= 1.0) | オプショナルなピア依存 |
|
|
570
|
+
| `@o3co/ts.hocon/adapters/yaml` | `yaml`(>= 2.0) | オプショナルなピア依存。スカラー解決はそのライブラリの答えで、`version: '1.2'` を明示してドリフトを防止 |
|
|
355
571
|
|
|
356
572
|
TOML と YAML のライブラリは **オプショナルなピア依存** なので、本パッケージを入れても
|
|
357
573
|
何も追加でインストールされません — 実際に使うものだけを足してください。プレーンな JSON に
|
|
@@ -372,11 +588,11 @@ cfg.getString('"foo.bar"') // "dotted" — 名前にドットを含む 1 つ
|
|
|
372
588
|
cfg.getString('foo.bar') // "nested" — foo -> bar
|
|
373
589
|
```
|
|
374
590
|
|
|
375
|
-
### JSONC / YAML の数値
|
|
591
|
+
### JSONC / JSON5 / YAML の数値
|
|
376
592
|
|
|
377
593
|
整数は精度を落とさずに取り込まれます。JS の `number` で表せない幅の整数リテラルも
|
|
378
|
-
桁がそのまま保持され、int64
|
|
379
|
-
|
|
594
|
+
桁がそのまま保持され、int64 の範囲を超える値は黙って壊れる代わりにエラーになります
|
|
595
|
+
(JSON5 の 16 進整数も同じです)。取得結果はゲッターによって変わります:
|
|
380
596
|
|
|
381
597
|
```ts
|
|
382
598
|
const cfg = parseJsonc('{"id": 9007199254740993}')
|
|
@@ -408,6 +624,14 @@ YAML のスカラー解決はそれ以外の点では `yaml` ライブラリの
|
|
|
408
624
|
- **include のパストラバーサル:** 相対 `include` パスは `baseDir` から解決され、`..` セグメントで外に出て `/etc/passwd` のような機密ファイルに到達し得ます。信頼できない入力をパースするなら、パスを検証するカスタム `readFileSync`/`readFile` を渡してください。
|
|
409
625
|
- **入力サイズ:** パーサーに入力サイズ上限はありません。信頼できない入力は `parse()` の前にサイズを検証してください。
|
|
410
626
|
- **include の深さ:** 深い include 連鎖によるスタックオーバーフローを防ぐため 50 段に制限されています。
|
|
627
|
+
- **写像されたパスの深さ:** 環境変数の `__` セグメントと `.properties` のドット区切りキーは
|
|
628
|
+
64 セグメントまでです。1 つの名前が任意の深さの連鎖を 1 本作るため、上限がなければ長い
|
|
629
|
+
変数名 1 つでスタックを使い切れてしまいました。rs.hocon と py.hocon も同じ写像を 64 で
|
|
630
|
+
制限しています。
|
|
631
|
+
- **ドキュメントのネストの深さ:** 上限はありません。エンジンのスタックを超えるほど深い
|
|
632
|
+
ドキュメントは `ParseError` か `ConfigError` で失敗します(素の `RangeError` にはなりません)。
|
|
633
|
+
ただし、どの深さで失敗するかは呼び出し側のコードがすでにどれだけ深いかに左右されるので、
|
|
634
|
+
決まった段数に頼らず、信頼できない入力はパース前にサイズを検証してください(上記)。
|
|
411
635
|
- **プロトタイプ汚染:** `__proto__` / `constructor` / `prototype` という名前のキーは
|
|
412
636
|
**通常のキーとして保持されます**。`.properties` ファイルや環境変数がそれらを正当に
|
|
413
637
|
使っていることがあり、落とせば黙ったデータ欠落になるからです。ライブラリ内部の安全性は
|