@joymerrevent/porters-connect 0.24.0 → 0.25.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/CHANGELOG.md CHANGED
@@ -5,6 +5,58 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.25.0] - 2026-09-25
9
+
10
+ **設定の誤りを黙って通さないようにし、トークンを期限つきで取り出せるようにした版**です。**破壊的変更を 2 つ**含みます
11
+ (定義していないオプションの拒否と、`porters.auth.getToken()` の戻り値)。あわせて、トークンの保存と中央のサービスからの
12
+ 受け取りの実践例を足し、README と目次を読みやすく直しました。
13
+
14
+ ### Changed
15
+
16
+ - **(破壊的)`new PortersClient(options)` と `porters.tenant(id, options)` は、定義していないオプションのキーを渡すと
17
+ `PortersConfigError`(`category: "config"`)になります**([ADR-0092][adr92])。これまでは打ち間違い(`hostName` など)や
18
+ 存在しないオプションを黙って無視し、誤った設定のまま動いていました。
19
+
20
+ ```ts
21
+ new PortersClient({ hostname, appId, appSecret, hostName: "x" });
22
+ // PortersConfigError: PortersClient: unknown option "hostName"
23
+ // hint: Valid options: hostname, port, scheme, appId, appSecret, scopes, tokenProvider, tokenStore, transport, throttle.
24
+ ```
25
+
26
+ - 値が `undefined` のキーは未指定と同じ扱いです(設定をスプレッドで組み立てると混ざりやすいため)。
27
+ - **アプリの設定オブジェクトを丸ごと渡している場合は、使うキーだけを取り出して渡してください。**
28
+ - `tenant(id, options)` に渡せるのは `fields` だけです。
29
+ - `PortersClientOptions` の型から、使えない項目だった `auth` と `fields` を外しました。
30
+
31
+ - **(破壊的)`porters.auth.getToken()` は、Access Token の文字列ではなく `{ token, expiresAt? }`(`IssuedToken`)を
32
+ 返すようになりました**([ADR-0093][adr93])。
33
+
34
+ ```ts
35
+ // 変更前
36
+ const token = await porters.auth.getToken();
37
+ // 変更後
38
+ const { token, expiresAt } = await porters.auth.getToken();
39
+ ```
40
+
41
+ - 返すのはリソースの呼び出しに使うのと同じトークンで、期限が近ければ取り直してから返します。`expiresAt` は
42
+ 1970-01-01 からのミリ秒で、取り方が期限を返さなかったときは省かれます。
43
+ - 中央のサービスが各アプリの `tokenProvider` にトークンを渡すとき、期限もそのまま渡せます。アプリのクライアントは
44
+ 期限切れで失敗する前に取り直せます。
45
+ - Refresh Token はこれまでどおり返しません。
46
+
47
+ - **使い方ドキュメントに実践例を 2 本足しました**:
48
+ [トークンを DB に保存する][recipe-token-store-db](`tokenStore` を Drizzle ORM と PostgreSQL で組む)と、
49
+ [中央のサービスからトークンを受け取る][recipe-central-token-service](App Secret を中央だけに置き、各アプリは
50
+ `tokenProvider` で受け取る)。
51
+
52
+ - **README と目次を見直しました**。README の「リソースと操作」の節を外して入口に絞り、見出し「最短で動かす」を
53
+ 「クイックスタート」に、各章の説明を概要の要約にしました。使い方ドキュメントの章の名前を「主題別」から
54
+ 「ガイド」に、「リソース別」から「リソース」に改めました(ページの場所は変わりません)。
55
+ `tokenProvider` に渡す関数が、取るのに要るもの(App Secret など)を自分で持つことも書き足しました。
56
+
57
+ - 内部の検査を変えました(利用者への影響はありません)。PR のミューテーションテストは変更したファイルだけに掛け、
58
+ `main` への PR は必ず全体を検査します。
59
+
8
60
  ## [0.24.0] - 2026-09-24
9
61
 
10
62
  **トークンの取り方と置き場所を別々に渡せるようにした版**です。**破壊的変更を 2 つ**含みます
@@ -1456,7 +1508,8 @@ Attachment)あるのに、受け口の形が 3 つとも違っていました
1456
1508
  [ref]: docs/usage/reference/README.md
1457
1509
  [kac]: https://keepachangelog.com/en/1.1.0/
1458
1510
  [semver]: https://semver.org/
1459
- [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.24.0...HEAD
1511
+ [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.25.0...HEAD
1512
+ [0.25.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.24.0...v0.25.0
1460
1513
  [0.24.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.23.0...v0.24.0
1461
1514
  [0.23.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.22.0...v0.23.0
1462
1515
  [0.22.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.21.0...v0.22.0
@@ -1502,4 +1555,8 @@ Attachment)あるのに、受け口の形が 3 つとも違っていました
1502
1555
  [adr89]: docs/adr/0089-custom-field-required-on-create.md
1503
1556
  [adr90]: docs/adr/0090-typescript-floor.md
1504
1557
  [adr91]: docs/adr/0091-token-provider-and-store.md
1558
+ [adr92]: docs/adr/0092-reject-unknown-options.md
1559
+ [adr93]: docs/adr/0093-get-token-with-expiry.md
1560
+ [recipe-token-store-db]: docs/usage/recipes/token-store-db.md
1561
+ [recipe-central-token-service]: docs/usage/recipes/central-token-service.md
1505
1562
  [troubleshooting]: docs/usage/reference/troubleshooting.md
package/README.md CHANGED
@@ -9,35 +9,39 @@ PORTERS Connect API(旧 HRBC)を **TypeScript から型安全・簡単に**
9
9
  > これは**非公式**ライブラリです。ポーターズ株式会社とは無関係で、公式ロゴ・商標は使用していません。
10
10
  > 利用には **PORTERS の契約 + Connect API オプション契約**が必要です(ホスト名・App ID/Secret は契約時に通知されます)。
11
11
 
12
- XML レスポンスを型付きオブジェクトに変換し、独自仕様の OAuth・レート制御・エラー整理を内側に隠します。
12
+ PORTERS が返す XML は、型の付いたオブジェクトに変換して返します。PORTERS 独自の OAuth、上限を守るための制御、
13
+ エラーの分類はライブラリが受け持ちます。
13
14
 
14
15
  ---
15
16
 
16
17
  ## 特徴
17
18
 
18
- - **型安全**:リソース・項目の値を型で表現。`any` を使いません。
19
- - **XML を外に出さない**:返り値は型付きオブジェクト、入力もふつうの JS の値。
20
- - **独自 OAuth を意識させない**:`code_direct` によるトークン取得・キャッシュ・更新を自動化。
21
- - **上限を守る**:スロットリング・リトライ(指数バックオフ)・リクエストの長さの検査を内蔵。
22
- - **日時は ISO 8601(UTC)に正規化**。業務タイムゾーン変換はしません(利用側の責務)。
23
- - **PORTERS の全リソースに対応**:データ系 13 種(Phase・Attachment を含む)+ マスタ系 5 種(読み取り専用)。
19
+ - **型安全**:リソースと項目の値に型が付きます。`any` は使いません。
20
+ - **XML を扱わなくてよい**:戻り値は型の付いたオブジェクトで、渡す値もふつうの JavaScript の値です。
21
+ - **OAuth の手続きを自動で行う**:`code_direct` でのトークンの取得・キャッシュ・更新をライブラリが行います。
22
+ - **上限を守る**:スロットリング、リトライ(指数バックオフ)、リクエストの長さの検査を備えています。
23
+ - **日時は ISO 8601(UTC)でやり取りする**:JST などへの変換はしません(利用側で行います)。
24
+ - **PORTERS の全リソースに対応**:マスタ系 5 種(読み取り専用)+ データ系 13 種(Phase・Attachment を含む)。
25
+ 一覧と呼べるメソッドは[リソースと操作][docs-resources]にあります。
24
26
 
25
27
  ## 前提
26
28
 
27
- 繋ぐ前に、**PORTERS 側で 4 つ**が要ります。揃っていないと PORTERS を呼べません。
29
+ 使い始める前に、次の **4 つ**を済ませておく必要があります。揃っていないと PORTERS を呼べません。
28
30
 
29
31
  1. **PORTERS 契約 + Connect API オプション契約**(オプションは別契約)。
30
- 2. **API アプリの登録**。ここで Redirect URL を決め、**ホスト名・App ID・App Secret** が
32
+ 2. **PORTERS への API アプリの登録**。ここで Redirect URL を決め、**ホスト名・App ID・App Secret** が
31
33
  通知されます(いずれも機密情報なので、コードに直接書かず環境変数で渡します)。
32
- 3. **初回のみブラウザで権限付与**(人手・Company DB ごとに 1 回)。以降はライブラリが
33
- `code_direct`(サーバ間)で無人運用します。
34
- 4. **付与するスコープ**の決定(リソース別に `_r` / `_w`。Read でも複数要ることがあります)。
34
+ 3. **初回だけ、ブラウザで権限を付与する**(人の操作が要ります。Company DB ごとに 1 回)。2 回目以降は、ライブラリが
35
+ `code_direct`(サーバ間)で人の操作なしにトークンを取ります。
36
+ 4. **付与するスコープを決める**(リソースごとに読み `_r` / 書き `_w`。読み取りだけでも複数のスコープが要ることがあります)。
35
37
 
36
- 揃えかたは[始める前に][s-prereq]に、権限付与の手順は[認証を通して、疎通を確認する][s-auth]に
37
- あります。実行環境は **Node.js 22.12 以上**で、型定義は同梱です(型を読むには **TypeScript 5.4 以上**が要ります)。ビルド済みの JavaScript ファイルは ESM(`import`)の 1 つですが、
38
- CJS(`require`)からも `require("@joymerrevent/porters-connect")` で読めます([CJS から使う][s-cjs])。
38
+ 揃え方は[始める前に][s-prereq]に、権限付与の手順は[認証を通して、疎通を確認する][s-auth]にあります。
39
39
 
40
- 契約や権限付与を**待っている間**も、PORTERS に繋がずにコードとテストは書けます
40
+ 実行環境は **Node.js 22.12 以上**です。型定義は同梱していて、型を読むには **TypeScript 5.4 以上**が要ります。
41
+ 配布しているのは ESM(`import`)のファイル 1 つですが、CJS(`require`)からも
42
+ `require("@joymerrevent/porters-connect")` で読み込めます([CJS から使う][s-cjs])。
43
+
44
+ 契約や権限付与を**待っている間**も、PORTERS に接続せずにコードとテストを書けます
41
45
  ([契約なしでテストする][test-without-contract])。
42
46
 
43
47
  ## インストール
@@ -48,7 +52,7 @@ npm i @joymerrevent/porters-connect
48
52
  # yarn add @joymerrevent/porters-connect
49
53
  ```
50
54
 
51
- ## 最短で動かす
55
+ ## クイックスタート
52
56
 
53
57
  ```ts
54
58
  import { PortersClient } from "@joymerrevent/porters-connect";
@@ -59,7 +63,7 @@ const porters = new PortersClient({
59
63
  appSecret: process.env.PORTERS_APP_SECRET ?? "",
60
64
  });
61
65
 
62
- // partition(Company DB)は tenant(id) で指定する(既定の Partition は無い)。単一テナントでも同じ書き方
66
+ // Partition(Company DB)は tenant(id) で指定する(既定の Partition は無い)。テナントが 1 つでも同じ書き方
63
67
  const t = porters.tenant(456);
64
68
 
65
69
  const page = await t.candidate.search({
@@ -72,74 +76,59 @@ const page = await t.candidate.search({
72
76
  console.log(page.total, page.items[0]?.P_Name);
73
77
  ```
74
78
 
75
- 続きは[導入][s-prereq](6 ページ)へ。準備・認証・読み取り・書き込み・本番に出す前の確認まで順に進みます。
76
-
77
- ## リソースと操作
78
-
79
- | アクセサ | リソース | アクセサ | リソース |
80
- | --------------- | -------------- | -------------- | ------------ |
81
- | `t.candidate` | 個人連絡先 | `t.contract` | 契約 |
82
- | `t.job` | JOB | `t.sales` | 成約・売上 |
83
- | `t.client` | 企業 | `t.process` | 選考プロセス |
84
- | `t.recruiter` | 企業担当者 | `t.resume` | レジュメ |
85
- | `t.contact` | コンタクト | `t.attachment` | 添付ファイル |
86
- | `t.opportunity` | 商談管理 | `t.phase` | フェーズ履歴 |
87
- | `t.activity` | アクティビティ | | |
88
-
89
- マスタ系は `porters.partition` / `t.user` / `t.department` / `t.field` / `t.option` の 5 種(読み取り専用)。
90
-
91
- **どのメソッドが呼べるかはリソースごとに違います**(`searchAll` が無いもの、先に `of("candidate")` のように
92
- 対象リソースを指定するもの(Field・Phase・Attachment)があります)。一覧は[リソースと操作][docs-resources]、引数・戻り値・項目の一覧は
93
- [API リファレンス][api-ref]が正確な定義です。
79
+ README で説明するのはここまでです。認証の準備・書き込み・エラーの扱いなど、使い方の全体は
80
+ [docs/usage][docs-index] の目次から読めます<!-- 根拠: ADR-0070(README は入口に絞る) -->。はじめての人は[導入][s-prereq](6 ページ)から順に進んでください。
94
81
 
95
82
  ## ドキュメント
96
83
 
97
84
  **[docs/usage][docs-index] が目次**です。7 つの章に分かれていて、順に読むのは導入だけです。
98
85
 
99
- | 章 | 何が書いてあるか |
100
- | ---------------- | ------------------------------------------------------------------------------------------- |
101
- | **導入** | 順に読む 6 ページ。準備 → インストール → 認証と疎通 → 読み → 書き → 本番前 |
102
- | **主題別** | 認証・検索・書き込み・カスタム項目・上限……の 11 主題を、考え方から細かい規則まで 1 ページで |
103
- | **クライアント** | `PortersClient`・`auth`・`tenant(id)` のスコープ。構築オプションと呼べるメソッド |
104
- | **リソース別** | 18 リソースを 1 ページずつ。呼べるメソッド・固有の注意・必須項目・型 |
105
- | **関数** | `import` して呼ぶ関数を用途別に。宣言と突合・上限と接続・値の変換 |
106
- | **実践例** | 毎日の差分同期・複数テナントなど、用途に沿った組み立て |
107
- | **リファレンス** | [公開 API リファレンス][api-ref](JSDoc から生成)と [PORTERS API の事実][ref] |
86
+ | 章 | 概要 |
87
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
88
+ | **導入** | PORTERS への接続から、データの読み書き、本番での運用までを、6 ページで順に説明します |
89
+ | **ガイド** | Partition・検索・書き込み・認証・上限など、PORTERS を使ううえで欠かせない事柄を 1 ページずつ説明します。PORTERS 側の前提と、ライブラリが受け持つ範囲が分かります |
90
+ | **クライアント** | クライアントの作り方と、クライアントから呼び出せる機能をまとめています |
91
+ | **リソース** | 18 のリソースごとに、使えるメソッド・注意点・新規作成の必須項目をまとめています |
92
+ | **関数** | `import` して使う関数ごとに、使い方と失敗したときの扱いをまとめています |
93
+ | **実践例** | 毎日の差分同期や複数テナントなど、よくある用途ごとに、機能の組み合わせ方をサンプルコードで示します |
94
+ | **リファレンス** | 型やメソッドの正確な定義([公開 API リファレンス][api-ref])と、PORTERS 側の仕様([PORTERS API の事実][ref])をまとめています |
108
95
 
109
- 目次の末尾に「〜したい → 読む場所」の索引表があります。
96
+ 目次の末尾の「目的から探す」から、やりたいことに合うページへ直接進めます。
110
97
 
111
98
  ## PORTERS 固有の注意
112
99
 
113
- このライブラリを使ううえで、**PORTERS 側の前提**として先に知っておくと迷いません。詳しくは
114
- それぞれの主題ページの「まず知ること」にあります。
100
+ PORTERS 側の仕様で、使い始める前に知っておきたいものです。詳しくは、それぞれのガイドのページの「まず知ること」に
101
+ あります。
115
102
 
116
- - **削除 API が存在しない**。`delete()` は型の上でも用意していません([削除と削除済みデータ][c-no-delete])。
117
- - **日時は UTC 前提**。ISO 8601(`…Z`)で入出力し、JST 等への変換はしません([日時と時分型][c-datetime])。
118
- - **データは Partition に分かれる**。`tenant(id)` で毎回指定します([Partition とテナントスコープ][c-partition])。
119
- - **上限がある**。リクエスト長 約 15000 文字・1 リクエスト 200 件・1 分あたり Read 2000 / Write 500 は
120
- ライブラリが守りますが、**月 15 万アクセスは契約条件**で利用側の運用責務です([上限とレート][c-limits])。
121
- - **ホスト名は非公開**。`PORTERS_HOST` で受け取り、ハードコードしません。
103
+ - **PORTERS には削除の API がありません**。このライブラリにも `delete()` はありません([削除と削除済みデータ][c-no-delete])。
104
+ - **日時は UTC です**。ISO 8601(`…Z`)で受け渡しし、JST などへの変換はしません([日時と時分型][c-datetime])。
105
+ - **データは Partition(Company DB)ごとに分かれています**。`tenant(id)` で Partition を指定してから読み書きします
106
+ ([Partition とテナントスコープ][c-partition])。
107
+ - **上限があります**。リクエストの長さ(約 15000 文字)・1 リクエスト 200 件・1 分あたり Read 2000 / Write 500 は
108
+ ライブラリが守ります。**月 15 万アクセスは契約の条件**で、数えて守るのは利用側です([上限とレート][c-limits])。
109
+ - **ホスト名は契約時に通知されます**。環境変数(`PORTERS_HOST` など)で渡し、コードに直接書かないでください。
122
110
 
123
111
  ## 対応バージョン
124
112
 
125
- - **互換性の基準は Connect API Version 2**:`X-P-ConnectAPI-Version: 2` を既定で送信し、**v2 を動作の前提**とします(担当者型・部署型の参照項目(Link)などは v2 が必要)。互換性はこの **API version** で明示します。
126
- - **PORTERS 製品 8.x / 9.x は参考**:v2 が提供される製品世代です(個別マイナーの動作保証はしません)。**正しい情報の出どころは [PORTERS API の事実][ref]**(PORTERS の公式ドキュメントに基づく)。
113
+ - **Connect API Version 2 を前提にしています**。リクエストには `X-P-ConnectAPI-Version: 2` を付けて送ります
114
+ (担当者型・部署型の参照項目(Link)などは v2 が必要です)。互換性は、この Connect API のバージョンで示します。
115
+ - **PORTERS の製品バージョン 8.x / 9.x は参考です**。どちらも v2 を提供している世代ですが、マイナーバージョンごとの動作は
116
+ 保証しません。PORTERS 側の仕様は [PORTERS API の事実][ref](PORTERS の公式ドキュメントに基づく)にまとめています。
127
117
 
128
118
  ## リンク
129
119
 
130
- **この README は「最短で動かす」ところまで**です。全体は目次から読めます<!-- 根拠: ADR-0070 -->。
131
-
132
120
  - 利用者向け:[docs/usage][docs-index](目次)/[公開 API リファレンス][api-ref]/[PORTERS API の事実][ref]
133
121
  - 開発・保守:[docs/README.md][docs-readme](ADR(設計判断の記録)・基本設計・ロードマップ・台帳)
134
122
  - 提供元:[Joymerrevent][joymerrevent]
135
123
 
136
124
  ## コントリビュート / セキュリティ
137
125
 
138
- - バグ報告・要望・質問は [Issues][issues] へ(外部からの提案は Issue 経由・PR 作成はコラボレーター限定)。詳しくは [CONTRIBUTING][contributing]。
126
+ - バグ報告・要望・質問は [Issues][issues] へお願いします。外部の方からの提案は Issue で受け付けていて、PR を作れるのは
127
+ コラボレーターだけです。詳しくは [CONTRIBUTING][contributing] にあります。
139
128
  - 脆弱性は公開 Issue ではなく [セキュリティポリシー][security] の手順で**非公開**で報告してください。
140
- - 行動規範:[Contributor Covenant][coc]。
129
+ - 行動規範は [Contributor Covenant][coc] です。
141
130
 
142
- > 念のため:本ライブラリは**非公式**です。PORTERS 製品・Connect API 本体の不具合や要望は PORTERS 公式へお願いします。
131
+ > このライブラリは**非公式**です。PORTERS の製品や Connect API そのものの不具合・要望は、PORTERS の公式窓口へお問い合わせください。
143
132
 
144
133
  ## ライセンス
145
134
 
package/dist/index.d.cts CHANGED
@@ -190,8 +190,14 @@ type AuthApi = {
190
190
  clearTokens(): Promise<void>;
191
191
  /** Acquire a token now (startup fail-fast / warm-up); throws if auth is unavailable. */
192
192
  ensureAuthenticated(): Promise<void>;
193
- /** Return the current valid Access Token (debug). The Refresh Token is never exposed. */
194
- getToken(): Promise<string>;
193
+ /**
194
+ * The Access Token the client currently uses, with its expiry — renewed first when it is within
195
+ * the refresh margin, exactly as a request would. `expiresAt` is epoch milliseconds, or absent
196
+ * when the token provider did not report one. Use it to hand the token to another process (for
197
+ * example, a central service answering its apps' `tokenProvider.acquire`). The Refresh Token is
198
+ * never exposed.
199
+ */
200
+ getToken(): Promise<IssuedToken>;
195
201
  };
196
202
 
197
203
  type DataType = "System[Id]" | "Number" | "DateTime" | "System[DateTime]" | "Date" | "Age" | "SinglelineText" | "MultilineText" | "Mail" | "Telephone" | "URL" | "User" | "Option" | "System[Reference]" | "System[Department]" | "Image" | "Link";
@@ -2947,12 +2953,6 @@ type PortersClientOptions = {
2947
2953
  tokenProvider?: TokenProvider;
2948
2954
  /** Token persistence, used with every token provider; defaults to in-memory. */
2949
2955
  tokenStore?: TokenStore;
2950
- /**
2951
- * **Not a client option any more.** Pass a `tokenProvider` (`{ acquire, refresh?, exchange? }`)
2952
- * instead; the client manages caching and renewal for it. Typed `never` so a leftover `auth`
2953
- * fails to compile; at runtime the constructor rejects it with {@link PortersConfigError}.
2954
- */
2955
- auth?: never;
2956
2956
  /** Injectable HTTP transport; defaults to a fetch-based transport. */
2957
2957
  transport?: Transport;
2958
2958
  /**
@@ -2964,14 +2964,6 @@ type PortersClientOptions = {
2964
2964
  * library leaves that to you. `createThrottle()` builds the default implementation.
2965
2965
  */
2966
2966
  throttle?: Throttle;
2967
- /**
2968
- * **Not a client option any more.** Custom fields belong to a partition, so the
2969
- * declaration goes to {@link PortersClient.tenant} as `tenant(id, { fields })`. Typed `never`
2970
- * so a configuration object that still carries the pre-0.21 `fields` fails to compile even when
2971
- * it is not a fresh literal; at runtime the constructor rejects it with {@link PortersConfigError}
2972
- * rather than silently dropping the declaration (the same fail-closed stance as `hostname`).
2973
- */
2974
- fields?: never;
2975
2967
  };
2976
2968
  /**
2977
2969
  * Options for {@link PortersClient.tenant}. `C` is inferred from `fields`.
package/dist/index.d.ts CHANGED
@@ -190,8 +190,14 @@ type AuthApi = {
190
190
  clearTokens(): Promise<void>;
191
191
  /** Acquire a token now (startup fail-fast / warm-up); throws if auth is unavailable. */
192
192
  ensureAuthenticated(): Promise<void>;
193
- /** Return the current valid Access Token (debug). The Refresh Token is never exposed. */
194
- getToken(): Promise<string>;
193
+ /**
194
+ * The Access Token the client currently uses, with its expiry — renewed first when it is within
195
+ * the refresh margin, exactly as a request would. `expiresAt` is epoch milliseconds, or absent
196
+ * when the token provider did not report one. Use it to hand the token to another process (for
197
+ * example, a central service answering its apps' `tokenProvider.acquire`). The Refresh Token is
198
+ * never exposed.
199
+ */
200
+ getToken(): Promise<IssuedToken>;
195
201
  };
196
202
 
197
203
  type DataType = "System[Id]" | "Number" | "DateTime" | "System[DateTime]" | "Date" | "Age" | "SinglelineText" | "MultilineText" | "Mail" | "Telephone" | "URL" | "User" | "Option" | "System[Reference]" | "System[Department]" | "Image" | "Link";
@@ -2947,12 +2953,6 @@ type PortersClientOptions = {
2947
2953
  tokenProvider?: TokenProvider;
2948
2954
  /** Token persistence, used with every token provider; defaults to in-memory. */
2949
2955
  tokenStore?: TokenStore;
2950
- /**
2951
- * **Not a client option any more.** Pass a `tokenProvider` (`{ acquire, refresh?, exchange? }`)
2952
- * instead; the client manages caching and renewal for it. Typed `never` so a leftover `auth`
2953
- * fails to compile; at runtime the constructor rejects it with {@link PortersConfigError}.
2954
- */
2955
- auth?: never;
2956
2956
  /** Injectable HTTP transport; defaults to a fetch-based transport. */
2957
2957
  transport?: Transport;
2958
2958
  /**
@@ -2964,14 +2964,6 @@ type PortersClientOptions = {
2964
2964
  * library leaves that to you. `createThrottle()` builds the default implementation.
2965
2965
  */
2966
2966
  throttle?: Throttle;
2967
- /**
2968
- * **Not a client option any more.** Custom fields belong to a partition, so the
2969
- * declaration goes to {@link PortersClient.tenant} as `tenant(id, { fields })`. Typed `never`
2970
- * so a configuration object that still carries the pre-0.21 `fields` fails to compile even when
2971
- * it is not a fresh literal; at runtime the constructor rejects it with {@link PortersConfigError}
2972
- * rather than silently dropping the declaration (the same fail-closed stance as `hostname`).
2973
- */
2974
- fields?: never;
2975
2967
  };
2976
2968
  /**
2977
2969
  * Options for {@link PortersClient.tenant}. `C` is inferred from `fields`.
package/dist/index.js CHANGED
@@ -725,6 +725,8 @@ var createTokenManager = (opts) => {
725
725
  };
726
726
  return {
727
727
  getAccessToken: async (o) => (await ensure(o?.forceRefresh ?? false)).accessToken.token,
728
+ // 写しを返す: 受け取った側が書き換えても、キャッシュの値(リクエストに使う値)は変わらない。
729
+ getIssuedToken: async () => ({ ...(await ensure(false)).accessToken }),
728
730
  cache: async (tokens) => {
729
731
  await save(requireTokens(tokens, "exchange"));
730
732
  },
@@ -792,7 +794,7 @@ var createAuthApi = (opts) => {
792
794
  },
793
795
  // `async` so a provider that throws synchronously still reaches the caller as a
794
796
  // rejection — a Promise-returning method never throws (ADR-0046).
795
- getToken: async () => opts.manager.getAccessToken()
797
+ getToken: async () => opts.manager.getIssuedToken()
796
798
  };
797
799
  };
798
800
 
@@ -2703,6 +2705,34 @@ var createOptionResource = (deps) => {
2703
2705
  };
2704
2706
 
2705
2707
  // src/client.ts
2708
+ var CLIENT_OPTION_KEYS = {
2709
+ hostname: true,
2710
+ port: true,
2711
+ scheme: true,
2712
+ appId: true,
2713
+ appSecret: true,
2714
+ scopes: true,
2715
+ tokenProvider: true,
2716
+ tokenStore: true,
2717
+ transport: true,
2718
+ throttle: true
2719
+ };
2720
+ var TENANT_OPTION_KEYS = {
2721
+ fields: true
2722
+ };
2723
+ var rejectUnknownKeys = (where, options, allowed) => {
2724
+ const unknown = Object.entries(options).filter(
2725
+ ([key, value]) => !Object.hasOwn(allowed, key) && value !== void 0
2726
+ ).map(([key]) => JSON.stringify(key));
2727
+ if (unknown.length === 0) return;
2728
+ throw new PortersConfigError(
2729
+ `${where}: unknown option${unknown.length > 1 ? "s" : ""} ${unknown.join(", ")}`,
2730
+ {
2731
+ category: "config",
2732
+ hint: `Valid options: ${Object.keys(allowed).join(", ")}.`
2733
+ }
2734
+ );
2735
+ };
2706
2736
  var validateTokenProvider = (provider) => {
2707
2737
  if (provider === void 0) return;
2708
2738
  const p = provider;
@@ -2763,24 +2793,7 @@ var PortersClient = class {
2763
2793
  tenant;
2764
2794
  #accessPoint;
2765
2795
  constructor(options) {
2766
- if (options.fields !== void 0) {
2767
- throw new PortersConfigError(
2768
- 'PortersClient: "fields" is not a client option \u2014 custom fields belong to a partition',
2769
- {
2770
- category: "config",
2771
- hint: "Declare them where you bind the partition: porters.tenant(id, { fields })"
2772
- }
2773
- );
2774
- }
2775
- if (options.auth !== void 0) {
2776
- throw new PortersConfigError(
2777
- 'PortersClient: "auth" is not a client option \u2014 pass a tokenProvider instead',
2778
- {
2779
- category: "config",
2780
- hint: "Replace auth: { getAccessToken } with tokenProvider: { acquire: async () => ({ accessToken: { token } }) }. The client caches and renews the token for you."
2781
- }
2782
- );
2783
- }
2796
+ rejectUnknownKeys("PortersClient", options, CLIENT_OPTION_KEYS);
2784
2797
  validateTokenProvider(options.tokenProvider);
2785
2798
  const accessPoint = {
2786
2799
  hostname: options.hostname,
@@ -2818,6 +2831,7 @@ var PortersClient = class {
2818
2831
  });
2819
2832
  this.#accessPoint = accessPoint;
2820
2833
  const buildScope = (partition, scope = {}) => {
2834
+ rejectUnknownKeys("tenant", scope, TENANT_OPTION_KEYS);
2821
2835
  const customFor = (key) => scope.fields?.[key] ?? {};
2822
2836
  const deps = { requester, accessPoint, partition };
2823
2837
  return {