@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.
- package/README.ja.md +253 -29
- package/README.md +161 -29
- package/dist/adapters/env.cjs +26 -2018
- package/dist/adapters/env.d.cts +1 -1
- package/dist/adapters/env.d.ts +1 -1
- package/dist/adapters/env.js +11 -1965
- package/dist/adapters/json5.cjs +15 -1969
- package/dist/adapters/json5.d.cts +1 -1
- package/dist/adapters/json5.d.ts +1 -1
- package/dist/adapters/json5.js +12 -1929
- package/dist/adapters/jsonc.cjs +17 -1972
- package/dist/adapters/jsonc.d.cts +1 -1
- package/dist/adapters/jsonc.d.ts +1 -1
- package/dist/adapters/jsonc.js +9 -1926
- package/dist/adapters/properties.cjs +7 -2113
- package/dist/adapters/properties.d.cts +1 -1
- package/dist/adapters/properties.d.ts +1 -1
- package/dist/adapters/properties.js +6 -2075
- package/dist/adapters/toml.cjs +12 -1966
- package/dist/adapters/toml.d.cts +1 -1
- package/dist/adapters/toml.d.ts +1 -1
- package/dist/adapters/toml.js +10 -1929
- package/dist/adapters/yaml.cjs +20 -1975
- package/dist/adapters/yaml.d.cts +1 -1
- package/dist/adapters/yaml.d.ts +1 -1
- package/dist/adapters/yaml.js +10 -1929
- package/dist/chunk-GOXMKYWS.js +3801 -0
- package/dist/chunk-JVABBNF5.js +158 -0
- package/dist/chunk-L3D2RE2U.cjs +3801 -0
- package/dist/chunk-OUPKYWL3.cjs +158 -0
- package/dist/chunk-PCAMDXJ4.cjs +83 -0
- package/dist/chunk-YRNDXYGQ.js +83 -0
- package/dist/{config-6sApTEMM.d.cts → config-OccaKqXu.d.cts} +126 -4
- package/dist/{config-6sApTEMM.d.ts → config-OccaKqXu.d.ts} +126 -4
- package/dist/index.cjs +34 -3793
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +25 -3728
- package/dist/zod.cjs +195 -3636
- package/dist/zod.d.cts +86 -3
- package/dist/zod.d.ts +86 -3
- package/dist/zod.js +182 -3589
- 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 ごと・リリースごとに、ビルド成果物に対して
|
|
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,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
|
-
-
|
|
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
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
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
|
-
| `
|
|
125
|
-
| `
|
|
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
|
|
158
|
-
// ResolveError
|
|
159
|
-
//
|
|
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 を含む) | **
|
|
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
|
-
|
|
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`
|
|
465
|
+
| 型付きゲッター | `getString`, `getNumber` 等(実行時に検査) | `get<T>()`(型パラメータのみ、実行時の検査なし) |
|
|
258
466
|
|
|
259
467
|
## ブラウザ対応
|
|
260
468
|
|
|
261
|
-
`parse()` と `parseAsync()`
|
|
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/
|
|
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
|
-
|
|
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
|
|
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
|
使っていることがあり、落とせば黙ったデータ欠落になるからです。ライブラリ内部の安全性は
|