@joymerrevent/porters-connect 0.9.0 → 0.11.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,150 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.11.0] - 2026-08-30
9
+
10
+ **参照先の項目を 1 往復で読めるようにし、`field` の語彙をクエリ全体と揃えた版**です。
11
+ どちらも「**要求したものが黙って返らない**」を潰す変更で、`field` に接頭辞を書かなくなる
12
+ **破壊的変更**を含みます(移行は接頭辞を消すだけ)。
13
+
14
+ ### Added
15
+
16
+ - **参照型の展開 `expand`**([ADR-0058][adr58])。`System[Reference]` の項目(`Job.P_Client` など)から
17
+ **参照先の項目そのもの**を取得できるようになりました。これまでは参照先の **ID だけ**を取り出し、
18
+ 入れ子で返ってきた残りを**黙って捨てて**いました。
19
+
20
+ ```ts
21
+ const page = await t.job.search({ expand: { P_Client: ["P_Id", "P_Name"] } });
22
+ page.items[0]?.P_Client; // { P_Id: number | null; P_Name: string | null } | null
23
+
24
+ const plain = await t.job.search();
25
+ plain.items[0]?.P_Client; // number | null(従来どおり)
26
+ ```
27
+
28
+ - **参照先の接頭辞は書きません**。`condition` / `order` / `field` と同じ素の alias で指定すると、
29
+ ライブラリが `field=Job.P_Client(Client.P_Id,Client.P_Name)` を組み立てます。
30
+ Candidate を参照するときの `Person.` も同様です。
31
+ - **`expand` を書いた項目だけ**戻り型が変わります。基底の型は `number | null` のままなので、
32
+ **参照を ID として使っているコードは無変更**です。展開しなかった参照も ID のまま返ります。
33
+ - `search` / `searchAll` / `get(id, { expand })` で使えます。`get` は第 2 引数が増えただけで、
34
+ 既存の `get(id)` はそのままです。
35
+ - 展開した alias は**素のエントリを置き換えて**送られます。同じ alias を `()` 有り・無しで 2 回送ったとき
36
+ どちらが優先されるかは PORTERS のドキュメントに記述が無いため、**そもそも送りません**。
37
+ - 展開できるのは**参照先をライブラリが実装している項目**だけです。`P_Recruiter`(Recruiter は未実装)と
38
+ カスタム項目(`U_`/`A_`)の参照型は対象外で、**従来どおり ID として読めます**。書くと型エラーです。
39
+ - `field` に `"Job.P_Client(Client.P_Id)"` のような展開文字列を書くと、送信前に
40
+ `PortersConfigError` で止まり `expand` を案内します。
41
+ - `()` の中に付ける参照先の接頭辞と入れ子の形は**実機で未確認**です
42
+ ([live-verification][lv] LV-10 / LV-16)。応答の解釈はタグ名に依存しない実装です。
43
+
44
+ - **型の export**: `Expand` / `ExpandedReadRecord` / `ReferenceMap` / `ResourcePageOf` /
45
+ `ReferenceRecord` / `ReadFieldAlias`。`FieldValue` に `ReferenceRecord`(展開された参照の値)が加わります。
46
+
47
+ ### Changed
48
+
49
+ - **(破壊的)Read の `field` は接頭辞なしの alias で書きます**([ADR-0059][adr59])。
50
+ 接頭辞はリソースごとの定数なので、**ライブラリが付けます**。
51
+
52
+ ```diff
53
+ - field: ["Person.P_Id", "Person.P_Name"]
54
+ + field: ["P_Id", "P_Name"]
55
+ ```
56
+
57
+ - 移行は**接頭辞を消すだけ**です。送られる URL は従来と同じで、変わるのは書き方と、
58
+ **どこで間違いに気づけるか**だけです。
59
+ - `condition` / `order` / `expand` はもともと素の alias を受けていたため、
60
+ **同じクエリの中で語彙が 2 つあった**状態が解消されます。
61
+ - **間違いがコンパイルエラーになります** — 綴り間違い(`P_Nmae`)・接頭辞付き(`Person.P_Name`)・
62
+ リソース名との取り違え(`Candidate.P_Name`。Candidate の接頭辞は `Person` です)・展開文字列。
63
+ これまでは型(`string[]`)でも送信前ガードでも素通りし、**PORTERS 側で黙って無視される**だけでした。
64
+ - **未宣言のカスタム項目は引き続き書けます**(`U_` / `A_` で始まる名前)。ただし `U_` 以降の綴りは
65
+ 検査できないので、よく使うものは `defineFields` で宣言してください(宣言すれば綴りも検査されます)。
66
+ - 実行時は接頭辞付きが来ても**剥がして受けます**(応答側と対称の寛容さ)。cast 経由の古い形も壊れません。
67
+ - 対象は `SearchQuery.field`(データ 5 リソース)と `UserSearchQuery.field`(マスタ User)。
68
+ **Attachment は元から接頭辞なし**なので変更ありません。`field: []`(主キーのみ)と
69
+ 省略時の既定 field([ADR-0020][adr20])の意味も変わりません。
70
+ - **pre-1.0 のため minor**([ADR-0055][adr55] と同じ扱い)。
71
+
72
+ - **明示した `field` も既定 field と同じ組み立てを通る**ようになりました。`User` 型の項目を
73
+ `field` で明示すると **4 サブ項目に展開**されます(`Job.P_Owner(User.P_Id,User.P_Type,User.P_Name,User.P_Mail)`)。
74
+ これまでは `()` 無しで送られ、PORTERS が ID を返すため、**型が `UserRef` を約束するのに実体は `null`**
75
+ になっていました。
76
+
77
+ ### Fixed
78
+
79
+ - **参照 ID の読み取りが、リソース名と alias 接頭辞の食い違いで `null` に落ちていました**。
80
+ 入れ子の**包みタグはリソース名**なのに**中の alias は接頭辞付き**で、この 2 つが異なる Candidate
81
+ (`<Candidate>` に `Person.P_Id`)では従来の照合が外れていました。**素の alias で照合**するようにしたので、
82
+ どちらの表記でも読めます。
83
+ - 影響していたのは `Process.P_Candidate` / `Resume.P_Candidate`(Candidate を参照する項目)です。
84
+ 包みタグの実際の値は**実機で未確認**([live-verification][lv] LV-10)で、フェイクサーバーは
85
+ これまで中立な包みを返していたため、テストでは露出していませんでした。
86
+
87
+ ## [0.10.0] - 2026-08-22
88
+
89
+ **partition の束ね方を 1 つに絞り、削除済みレコードを判別できるようにした版**です。
90
+ `PortersClient` から `partition` を外した**破壊的変更**を含みます(移行は下記)。
91
+ 併せて、削除済みを読めるのに**どれが削除済みか分からなかった**穴を塞ぎました。
92
+
93
+ ### Added
94
+
95
+ - **削除フラグ `P_Deleted`**([ADR-0056][adr56])。データ系 5 リソース(Candidate / Job / Client /
96
+ Process / Resume)の公開型に加わり、`itemstate: "all"` の結果から削除済みを**判別できる**ようになりました。
97
+
98
+ ```ts
99
+ const page = await t.candidate.search({ itemstate: "all" });
100
+ const deleted = page.items.filter((c) => c.P_Deleted === "1");
101
+ ```
102
+
103
+ - **値は文字列**の `"0"`(生存)/`"1"`(削除済み)です。`number` でも `boolean` でもありません。
104
+ PORTERS がこの項目に **Data Type を与えていない**(reference の Field Type / Data Type 欄がともに「ー」)ため、
105
+ 変換の基準がありません。勝手に決めればライブラリの発明になるので、**生の値のまま**返します。
106
+ - **`condition` にも `order` にも指定できず、Write もできません**(PORTERS の制約)。
107
+ いずれも型で表現されているので、書くとコンパイルエラーになります。
108
+ - `field` を省略すれば**自動で要求**されます。自分で `field` を渡すときは
109
+ `"Person.P_Deleted"` のように接頭辞付きで明示してください。
110
+ - 削除 API が無い PORTERS では「削除済みを読む」こと自体が運用手段(同期・監査・復元の判断材料)ですが、
111
+ これまでは**読めるのに解釈できない**状態でした。
112
+ - 応答での出現条件と値域は**実機で未確認**です([live-verification][lv] LV-14)。
113
+
114
+ ### Changed
115
+
116
+ - **(破壊的)`PortersClient` から `partition` を外しました**([ADR-0055][adr55])。
117
+ partition(Company DB)スコープのリソースは **`porters.tenant(id)` 経由でのみ**取得します。
118
+ **単一テナントでも同じ形**です。
119
+
120
+ ```diff
121
+ - const porters = new PortersClient({ host, appId, appSecret, partition: 123 });
122
+ - await porters.candidate.search();
123
+ + const porters = new PortersClient({ host, appId, appSecret });
124
+ + const t = porters.tenant(123);
125
+ + await t.candidate.search();
126
+ ```
127
+
128
+ - 影響するのは `candidate` / `job` / `client` / `process` / `resume` / `attachment` /
129
+ `user` / `field` / `option` の 9 アクセサ。`t` は以降クライアントのように使えるので、
130
+ **呼び出し側の書き味は変わりません**。
131
+ - **App レベルのものは `porters` 側に残ります**(いずれも `partition` を取りません)—
132
+ `porters.auth.*`(OAuth)と `porters.partition.search()`(partition の発見)。
133
+ - 理由: `partition` 未設定のクライアントは**`partition=0`** を全リクエストに載せていました。
134
+ `0` は PORTERS のドキュメントに存在しない値で、由来は最初の PoC の穴埋めです。実際には
135
+ Result Code **404** を招くため、**設定漏れが「サーバーが 404 を返す」形に化け**ていました。
136
+ `tenant(id)` を唯一の経路にすることで、**「partition を束ね忘れたクライアント」という状態が
137
+ 型として存在しなくなります**。実行時ガードを足すのではなく、設計で防ぐ形にしました。
138
+ - **pre-1.0 のため minor**。誤った partition を渡すこと自体は妨げません(404 はサーバーが答えます)。
139
+
140
+ ### Fixed
141
+
142
+ - **`itemstate: "existing"` を明示指定したとき、省略せずそのまま送る**ようになりました
143
+ ([ADR-0057][adr57])。**返るデータは変わりません** — 変わるのは送信 URL だけです。
144
+ - これまでは `existing` が API の既定であることを理由に、明示指定でも param を省いていました。
145
+ しかし**省略(「既定に委ねる」)と明示(「生存レコードのみが欲しい」)は別の意思表示**です。
146
+ いま結果が同じなのは PORTERS の既定が `existing` だからで、**もし将来この既定が変われば、
147
+ 生存のみを求めたコードに削除済みが混ざり始めます**(例外も Result Code も出ないので気づけません)。
148
+ - 生存のみであることが業務上重要なら、省略せず `itemstate: "existing"` と書いてください。
149
+ 省略した場合はこれまでどおり API の既定に従います。
150
+ - `itemstate=existing` の実機送信実績はまだありません([live-verification][lv] LV-15)。
151
+
8
152
  ## [0.9.0] - 2026-08-20
9
153
 
10
154
  **Candidate でメモ・住所詳細が扱えるようになった版**です。標準項目でありながらカタログから漏れていた
@@ -288,9 +432,17 @@
288
432
  [adr35]: docs/adr/0035-usage-documentation-structure.md
289
433
  [custom-fields-guide]: docs/guide/custom-fields.md
290
434
  [read-query-guide]: docs/guide/read-query.md
435
+ [adr55]: docs/adr/0055-partition-binding-guard.md
436
+ [adr56]: docs/adr/0056-deleted-flag-typing.md
437
+ [adr57]: docs/adr/0057-itemstate-existing-explicit.md
438
+ [adr58]: docs/adr/0058-reference-expansion-read.md
439
+ [adr59]: docs/adr/0059-read-field-bare-alias.md
440
+ [lv]: docs/live-verification.md
291
441
  [kac]: https://keepachangelog.com/en/1.1.0/
292
442
  [semver]: https://semver.org/
293
- [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.9.0...HEAD
443
+ [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.11.0...HEAD
444
+ [0.11.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.10.0...v0.11.0
445
+ [0.10.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.9.0...v0.10.0
294
446
  [0.9.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.8.0...v0.9.0
295
447
  [0.8.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.7.0...v0.8.0
296
448
  [0.7.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.6.2...v0.7.0
package/README.md CHANGED
@@ -58,15 +58,14 @@ const porters = new PortersClient({
58
58
  scheme: process.env.PORTERS_SCHEME === "http" ? "http" : undefined,
59
59
  appId: process.env.PORTERS_APP_ID!,
60
60
  appSecret: process.env.PORTERS_APP_SECRET!,
61
- partition: 123, // 既定 Partition(Company DB)Id
62
61
  });
63
62
 
64
- // 複数 partition を 1 つの App で扱う SaaS は porters.tenant(id) で束ねる
63
+ // partition(Company DB)は tenant で一度だけ束ねる。**単一テナントでもこの形**
64
+ // 以降は `t` をクライアントのように使う(client 直下には auth と partition マスタだけ)
65
65
  const t = porters.tenant(456);
66
- await t.candidate.search({ condition: { P_Name: { part: "鈴木" } } }); // partition=456
67
66
 
68
67
  // 検索(最大 200 件/ページ)。condition は項目の Data Type ごとに型付き
69
- const page = await porters.candidate.search({
68
+ const page = await t.candidate.search({
70
69
  condition: { P_Name: { part: "山田" } }, // テキストは part(部分一致)/ full(完全一致)
71
70
  order: [{ P_UpdateDate: "desc" }], // 並び順(数値・日時・System のみ)
72
71
  count: 50,
@@ -74,18 +73,21 @@ const page = await porters.candidate.search({
74
73
  console.log(page.total, page.items.length);
75
74
 
76
75
  // 1 件取得
77
- const one = await porters.candidate.get(10001);
76
+ const one = await t.candidate.get(10001);
78
77
  console.log(one?.P_Name);
79
78
 
80
79
  // 全件を自動ページング(200 件刻み)
81
- for await (const c of porters.candidate.searchAll({
80
+ for await (const c of t.candidate.searchAll({
82
81
  condition: { P_Prefecture: { full: "東京都" } },
83
82
  })) {
84
83
  // c は 1 件ずつ
85
84
  }
86
85
  ```
87
86
 
88
- > 複数 partition を 1 つの App で扱う(マルチテナント SaaS)場合の `porters.tenant(id)` スコープ・テナント別 client・partition 発見は [マルチテナント ガイド][multi-tenancy] にまとめています。
87
+ > **`partition` はクライアントに持たせません**(ADR-0055)。PORTERS は partition スコープの全リクエストで
88
+ > `partition` を要求するので、`tenant(id)` で**明示的に一度だけ**束ねる形にしています。
89
+ > 複数 partition を 1 つの App で扱う場合・テナント別 client・partition の発見は
90
+ > [マルチテナント ガイド][multi-tenancy] にまとめています。
89
91
  >
90
92
  > 認証情報やホスト名は**コミットしない**でください。`.env.example` を参考に `.env` で渡します。
91
93
 
@@ -103,7 +105,6 @@ const porters = new PortersClient({
103
105
  host: "sandbox.invalid",
104
106
  appId: "demo",
105
107
  appSecret: "demo",
106
- partition: 1,
107
108
  transport: createMockTransport((req) =>
108
109
  req.url.includes("/v1/candidate")
109
110
  ? `<Candidate Total="1" Count="1" Start="0"><Code>0</Code><Item><Person.P_Id>1</Person.P_Id><Person.P_Name>山田 太郎</Person.P_Name></Item></Candidate>`
@@ -111,7 +112,7 @@ const porters = new PortersClient({
111
112
  ), // 未モックのリクエストは明示エラー(フェイルセーフ)
112
113
  });
113
114
 
114
- const page = await porters.candidate.search();
115
+ const page = await t.candidate.search();
115
116
  console.log(page.items[0]?.P_Name); // 山田 太郎
116
117
  ```
117
118
 
@@ -141,7 +142,7 @@ const tokenStore: TokenStore = {
141
142
  /* 破棄 */
142
143
  },
143
144
  };
144
- new PortersClient({ host, appId, appSecret, partition, tokenStore });
145
+ new PortersClient({ host, appId, appSecret, tokenStore });
145
146
  ```
146
147
 
147
148
  `transport`(HTTP 注入)や `auth`(独自 `TokenProvider`)も差し替え可能です。
@@ -166,26 +167,29 @@ await porters.auth.exchangeAuthorizationCode(code);
166
167
 
167
168
  すべてのデータ系リソースは同じ形のアクセサを持ちます。
168
169
 
169
- | アクセサ | リソース | メソッド |
170
- | -------------------- | ------------ | ---------------------------------------------------- |
171
- | `porters.candidate` | 個人連絡先 | `search` / `searchAll` / `get` / `create` / `update` |
172
- | `porters.job` | JOB | `search` / `searchAll` / `get` / `create` / `update` |
173
- | `porters.client` | 企業 | `search` / `searchAll` / `get` / `create` / `update` |
174
- | `porters.process` | 選考プロセス | `search` / `searchAll` / `get` / `create` / `update` |
175
- | `porters.resume` | レジュメ | `search` / `searchAll` / `get` / `create` / `update` |
176
- | `porters.attachment` | 添付ファイル | `search` / `get` / `create` / `update` |
170
+ | アクセサ | リソース | メソッド |
171
+ | -------------- | ------------ | ---------------------------------------------------- |
172
+ | `t.candidate` | 個人連絡先 | `search` / `searchAll` / `get` / `create` / `update` |
173
+ | `t.job` | JOB | `search` / `searchAll` / `get` / `create` / `update` |
174
+ | `t.client` | 企業 | `search` / `searchAll` / `get` / `create` / `update` |
175
+ | `t.process` | 選考プロセス | `search` / `searchAll` / `get` / `create` / `update` |
176
+ | `t.resume` | レジュメ | `search` / `searchAll` / `get` / `create` / `update` |
177
+ | `t.attachment` | 添付ファイル | `search` / `get` / `create` / `update` |
177
178
 
178
179
  - `search(query?)` → `{ items, total, count, start }`(オフセット式ページング)。
179
180
  - `searchAll(query?)` → `AsyncIterable`(200 件刻みで全件 yield)。
180
- - `get(id)` → 1 件 or `undefined`。
181
+ - `get(id, options?)` → 1 件 or `undefined`(`options.expand` で参照先の項目も読めます)。
181
182
  - `create(input)` → 採番された **id(number)**。
182
183
  - `update(id, input)` → その **id**。
183
184
 
184
185
  **検索クエリ**(`query`)の主なキー(すべて型安全。**項目の Data Type が許す演算子だけ**を受けます):
185
186
 
186
- - `field`:取得する項目(接頭辞付き alias の配列。例 `["Person.P_Id", "Person.P_Name"]`)。
187
+ - `field`:取得する項目(**接頭辞なし**の alias の配列。例 `["P_Id", "P_Name"]`。接頭辞はライブラリが付けます)。
188
+ 綴り間違いや接頭辞付きは**コンパイルエラー**になります。
187
189
  **省略するとカタログ上の全項目を既定取得**します(PORTERS は field 未指定だと主キーのみ返すため、
188
190
  ライブラリが既定 field を補います)。`field: []`(空配列)を渡すと API 仕様どおり**主キーのみ**を返します(件数取得など)。
191
+ - `expand`:参照型(`System[Reference]`)の項目について、**参照先の項目も読む**(`{ P_Client: ["P_Id", "P_Name"] }`)。
192
+ 書かなければ従来どおり**参照先の ID**が返り、**書いた項目だけ**戻り型が参照レコードに変わります。1 往復で済みます。
189
193
  - `condition`:検索条件。`{ 項目: { 演算子: 値 } }` 形式(複数項目は AND)。演算子は Data Type ごとに
190
194
  `eq`/`gt`/`ge`/`le`/`lt`(数値・日時・Id)、`part`/`full`(テキスト)、`or`/`and`(Option・参照/ユーザー型は ID)。
191
195
  **日時の値は ISO 8601(UTC `…Z`)**で渡すと PORTERS 形式へ自動変換します。
@@ -197,8 +201,9 @@ await porters.auth.exchangeAuthorizationCode(code);
197
201
  > **削除 API はありません**(PORTERS 仕様)。`delete()` メソッドは提供しません。削除済みは `itemstate: "deleted"` で読みます
198
202
  > (`condition` は `P_Id` / `P_UpdateDate` / `P_UpdatedBy` に限られ、更新日は 90 日以内)。
199
203
  >
200
- > `field` の 3 通りの意味(省略=全項目 / `[]`=主キーのみ / 明示)、Data Type ごとの演算子一覧、
201
- > 削除済み Read の制約、送信前に落ちる条件は [Read クエリ ガイド][read-query-guide] にまとめています。
204
+ > `field` の 3 通りの意味(省略=全項目 / `[]`=主キーのみ / 明示)、`expand` で展開できる項目、
205
+ > Data Type ごとの演算子一覧、削除済み Read の制約、送信前に落ちる条件は
206
+ > [Read クエリ ガイド][read-query-guide] にまとめています。
202
207
 
203
208
  ### カスタム項目(`U_` / `A_`)
204
209
 
@@ -214,11 +219,10 @@ const porters = new PortersClient({
214
219
  host,
215
220
  appId,
216
221
  appSecret,
217
- partition,
218
222
  fields,
219
223
  });
220
224
 
221
- const one = await porters.candidate.get(10001);
225
+ const one = await t.candidate.get(10001);
222
226
  one?.U_score; // number | null | undefined
223
227
  ```
224
228
 
@@ -234,25 +238,25 @@ one?.U_score; // number | null | undefined
234
238
  | アクセサ | リソース | メソッド | 主なクエリ |
235
239
  | ------------------- | ----------------- | ---------------------------------- | ----------------------------------------- |
236
240
  | `porters.partition` | Partition | `search` / `searchAll` | `requestType`(1=アクセス可能一覧・既定) |
237
- | `porters.user` | User | `search` / `searchAll` / `current` | `requestType` / `userType` / `field` |
238
- | `porters.field` | Field(項目定義) | `search` / `searchAll` | `resource`(必須)/ `active` |
239
- | `porters.option` | Option(選択肢) | `search` | `alias` / `level` / `enabled` |
241
+ | `t.user` | User | `search` / `searchAll` / `current` | `requestType` / `userType` / `field` |
242
+ | `t.field` | Field(項目定義) | `search` / `searchAll` | `resource`(必須)/ `active` |
243
+ | `t.option` | Option(選択肢) | `search` | `alias` / `level` / `enabled` |
240
244
 
241
245
  ```ts
242
246
  // アクセス可能な Partition(Company DB)を発見
243
247
  const partitions = await porters.partition.search();
244
248
 
245
249
  // 現在の API ユーザー(code_direct ではアプリ自身の User)=自己同定
246
- const me = await porters.user.current();
250
+ const me = await t.user.current();
247
251
 
248
252
  // Job リソースの項目定義(U_/A_ カスタム項目を含む)を取得
249
- const fields = await porters.field.search({ resource: "job" });
253
+ const fields = await t.field.search({ resource: "job" });
250
254
 
251
255
  // 選択肢マスタをフラットな配列で取得(親子は P_ParentId で復元)
252
- const options = await porters.option.search({ alias: "Option.P_Gender" });
256
+ const options = await t.option.search({ alias: "Option.P_Gender" });
253
257
  ```
254
258
 
255
- - `porters.option.search()` は入れ子の選択肢ツリーを**深さ優先でフラット化**して返します(全ノード・`P_ParentId`/`P_Order` で階層復元可)。`start` が無いため `searchAll` はありません。
259
+ - `t.option.search()` は入れ子の選択肢ツリーを**深さ優先でフラット化**して返します(全ノード・`P_ParentId`/`P_Order` で階層復元可)。`start` が無いため `searchAll` はありません。
256
260
  - `porters.partition.current()` は提供しません(`request_type=0` は既定の `code_direct` 認証では 403 になるため)。一覧は `search()`(既定 `requestType: 1`)で取得します。
257
261
 
258
262
  ## 読み取り値の表現
@@ -272,7 +276,7 @@ PORTERS の Field Type に応じてデコードされます。
272
276
  | 空・未設定 | `null` |
273
277
 
274
278
  ```ts
275
- const c = await porters.candidate.get(10001);
279
+ const c = await t.candidate.get(10001);
276
280
  c?.P_Id; // number
277
281
  c?.P_Name; // string | null
278
282
  c?.P_RegistrationDate; // "2026-01-02T03:04:05Z" | null
@@ -295,17 +299,17 @@ c?.P_Owner; // { P_Id, P_Name, ... } | null
295
299
 
296
300
  ```ts
297
301
  // 作成(新規)
298
- const newId = await porters.candidate.create({
302
+ const newId = await t.candidate.create({
299
303
  P_Owner: 5, // User 項目は id
300
304
  P_Name: "鈴木 一郎",
301
305
  P_Reading: "すずき いちろう",
302
306
  });
303
307
 
304
308
  // 更新
305
- await porters.candidate.update(newId, { P_Mail: "ichiro@example.com" });
309
+ await t.candidate.update(newId, { P_Mail: "ichiro@example.com" });
306
310
 
307
311
  // 選考プロセス(関連 id を指定)
308
- await porters.process.create({
312
+ await t.process.create({
309
313
  P_Owner: 1,
310
314
  P_Client: 100,
311
315
  P_Recruiter: 200,
@@ -315,7 +319,7 @@ await porters.process.create({
315
319
  });
316
320
 
317
321
  // 一括作成/更新(200 件+サイズで自動分割・部分成功を返す)
318
- const bulk = await porters.candidate.createMany([
322
+ const bulk = await t.candidate.createMany([
319
323
  { P_Owner: 5, P_Name: "山田 太郎" },
320
324
  { P_Owner: 5, P_Name: "鈴木 花子" },
321
325
  ]);
@@ -331,7 +335,7 @@ if (bulk.hasFailures) console.warn(bulk.failed); // per-item は throw せず結
331
335
  ```ts
332
336
  import { bytesToBase64 } from "@joymerrevent/porters-connect";
333
337
 
334
- const id = await porters.attachment.create({
338
+ const id = await t.attachment.create({
335
339
  resource: 17, // 関連リソース種別コード(Resource List 参照)
336
340
  resourceId: 10001, // 関連レコードの id
337
341
  contentType: "application/pdf",
@@ -351,7 +355,7 @@ const id = await porters.attachment.create({
351
355
  import { PortersError } from "@joymerrevent/porters-connect";
352
356
 
353
357
  try {
354
- await porters.candidate.get(1);
358
+ await t.candidate.get(1);
355
359
  } catch (e) {
356
360
  if (e instanceof PortersError) {
357
361
  e.category; // "auth" | "permission" | "notFound" | "conflict" | "network" | ...
@@ -365,7 +369,7 @@ try {
365
369
 
366
370
  - トークン失効は内側で自動回復します。設定ミスは `PortersConfigError` で早期に落とします。
367
371
  - **`Promise` を返す公開メソッドは同期 throw しません**([ADR-0046][adr46])。設定ミスも含め常に **reject** で届くので、
368
- `porters.candidate.search(q).catch(handler)` でも捕まえられます(`string` を返す `auth.authorizationUrl` や
372
+ `t.candidate.search(q).catch(handler)` でも捕まえられます(`string` を返す `auth.authorizationUrl` や
369
373
  コンストラクタなど、Promise を返さない API は同期 throw のままです)。
370
374
  - 一時エラー・ネットワークは内蔵リトライ。非冪等な `create` はネットワーク不確実時に握り潰さず表面化します。
371
375
  - レート制限超過時、PORTERS は判別可能なコードを返さず接続を切るため、`PortersNetworkError`(category `"network"`)として表面化します。