@joymerrevent/porters-connect 0.9.0 → 0.10.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 +71 -1
- package/README.md +35 -35
- package/dist/index.d.ts +55 -40
- package/dist/index.js +45 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,71 @@
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.10.0] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
**partition の束ね方を 1 つに絞り、削除済みレコードを判別できるようにした版**です。
|
|
11
|
+
`PortersClient` から `partition` を外した**破壊的変更**を含みます(移行は下記)。
|
|
12
|
+
併せて、削除済みを読めるのに**どれが削除済みか分からなかった**穴を塞ぎました。
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **削除フラグ `P_Deleted`**([ADR-0056][adr56])。データ系 5 リソース(Candidate / Job / Client /
|
|
17
|
+
Process / Resume)の公開型に加わり、`itemstate: "all"` の結果から削除済みを**判別できる**ようになりました。
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const page = await t.candidate.search({ itemstate: "all" });
|
|
21
|
+
const deleted = page.items.filter((c) => c.P_Deleted === "1");
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **値は文字列**の `"0"`(生存)/`"1"`(削除済み)です。`number` でも `boolean` でもありません。
|
|
25
|
+
PORTERS がこの項目に **Data Type を与えていない**(reference の Field Type / Data Type 欄がともに「ー」)ため、
|
|
26
|
+
変換の基準がありません。勝手に決めればライブラリの発明になるので、**生の値のまま**返します。
|
|
27
|
+
- **`condition` にも `order` にも指定できず、Write もできません**(PORTERS の制約)。
|
|
28
|
+
いずれも型で表現されているので、書くとコンパイルエラーになります。
|
|
29
|
+
- `field` を省略すれば**自動で要求**されます。自分で `field` を渡すときは
|
|
30
|
+
`"Person.P_Deleted"` のように接頭辞付きで明示してください。
|
|
31
|
+
- 削除 API が無い PORTERS では「削除済みを読む」こと自体が運用手段(同期・監査・復元の判断材料)ですが、
|
|
32
|
+
これまでは**読めるのに解釈できない**状態でした。
|
|
33
|
+
- 応答での出現条件と値域は**実機で未確認**です([live-verification][lv] LV-14)。
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **(破壊的)`PortersClient` から `partition` を外しました**([ADR-0055][adr55])。
|
|
38
|
+
partition(Company DB)スコープのリソースは **`porters.tenant(id)` 経由でのみ**取得します。
|
|
39
|
+
**単一テナントでも同じ形**です。
|
|
40
|
+
|
|
41
|
+
```diff
|
|
42
|
+
- const porters = new PortersClient({ host, appId, appSecret, partition: 123 });
|
|
43
|
+
- await porters.candidate.search();
|
|
44
|
+
+ const porters = new PortersClient({ host, appId, appSecret });
|
|
45
|
+
+ const t = porters.tenant(123);
|
|
46
|
+
+ await t.candidate.search();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- 影響するのは `candidate` / `job` / `client` / `process` / `resume` / `attachment` /
|
|
50
|
+
`user` / `field` / `option` の 9 アクセサ。`t` は以降クライアントのように使えるので、
|
|
51
|
+
**呼び出し側の書き味は変わりません**。
|
|
52
|
+
- **App レベルのものは `porters` 側に残ります**(いずれも `partition` を取りません)—
|
|
53
|
+
`porters.auth.*`(OAuth)と `porters.partition.search()`(partition の発見)。
|
|
54
|
+
- 理由: `partition` 未設定のクライアントは**`partition=0`** を全リクエストに載せていました。
|
|
55
|
+
`0` は PORTERS のドキュメントに存在しない値で、由来は最初の PoC の穴埋めです。実際には
|
|
56
|
+
Result Code **404** を招くため、**設定漏れが「サーバーが 404 を返す」形に化け**ていました。
|
|
57
|
+
`tenant(id)` を唯一の経路にすることで、**「partition を束ね忘れたクライアント」という状態が
|
|
58
|
+
型として存在しなくなります**。実行時ガードを足すのではなく、設計で防ぐ形にしました。
|
|
59
|
+
- **pre-1.0 のため minor**。誤った partition を渡すこと自体は妨げません(404 はサーバーが答えます)。
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **`itemstate: "existing"` を明示指定したとき、省略せずそのまま送る**ようになりました
|
|
64
|
+
([ADR-0057][adr57])。**返るデータは変わりません** — 変わるのは送信 URL だけです。
|
|
65
|
+
- これまでは `existing` が API の既定であることを理由に、明示指定でも param を省いていました。
|
|
66
|
+
しかし**省略(「既定に委ねる」)と明示(「生存レコードのみが欲しい」)は別の意思表示**です。
|
|
67
|
+
いま結果が同じなのは PORTERS の既定が `existing` だからで、**もし将来この既定が変われば、
|
|
68
|
+
生存のみを求めたコードに削除済みが混ざり始めます**(例外も Result Code も出ないので気づけません)。
|
|
69
|
+
- 生存のみであることが業務上重要なら、省略せず `itemstate: "existing"` と書いてください。
|
|
70
|
+
省略した場合はこれまでどおり API の既定に従います。
|
|
71
|
+
- `itemstate=existing` の実機送信実績はまだありません([live-verification][lv] LV-15)。
|
|
72
|
+
|
|
8
73
|
## [0.9.0] - 2026-08-20
|
|
9
74
|
|
|
10
75
|
**Candidate でメモ・住所詳細が扱えるようになった版**です。標準項目でありながらカタログから漏れていた
|
|
@@ -288,9 +353,14 @@
|
|
|
288
353
|
[adr35]: docs/adr/0035-usage-documentation-structure.md
|
|
289
354
|
[custom-fields-guide]: docs/guide/custom-fields.md
|
|
290
355
|
[read-query-guide]: docs/guide/read-query.md
|
|
356
|
+
[adr55]: docs/adr/0055-partition-binding-guard.md
|
|
357
|
+
[adr56]: docs/adr/0056-deleted-flag-typing.md
|
|
358
|
+
[adr57]: docs/adr/0057-itemstate-existing-explicit.md
|
|
359
|
+
[lv]: docs/live-verification.md
|
|
291
360
|
[kac]: https://keepachangelog.com/en/1.1.0/
|
|
292
361
|
[semver]: https://semver.org/
|
|
293
|
-
[unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.
|
|
362
|
+
[unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.10.0...HEAD
|
|
363
|
+
[0.10.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.9.0...v0.10.0
|
|
294
364
|
[0.9.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.8.0...v0.9.0
|
|
295
365
|
[0.8.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.7.0...v0.8.0
|
|
296
366
|
[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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
-
>
|
|
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
|
|
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,
|
|
145
|
+
new PortersClient({ host, appId, appSecret, tokenStore });
|
|
145
146
|
```
|
|
146
147
|
|
|
147
148
|
`transport`(HTTP 注入)や `auth`(独自 `TokenProvider`)も差し替え可能です。
|
|
@@ -166,14 +167,14 @@ await porters.auth.exchangeAuthorizationCode(code);
|
|
|
166
167
|
|
|
167
168
|
すべてのデータ系リソースは同じ形のアクセサを持ちます。
|
|
168
169
|
|
|
169
|
-
| アクセサ
|
|
170
|
-
|
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
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)。
|
|
@@ -214,11 +215,10 @@ const porters = new PortersClient({
|
|
|
214
215
|
host,
|
|
215
216
|
appId,
|
|
216
217
|
appSecret,
|
|
217
|
-
partition,
|
|
218
218
|
fields,
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
-
const one = await
|
|
221
|
+
const one = await t.candidate.get(10001);
|
|
222
222
|
one?.U_score; // number | null | undefined
|
|
223
223
|
```
|
|
224
224
|
|
|
@@ -234,25 +234,25 @@ one?.U_score; // number | null | undefined
|
|
|
234
234
|
| アクセサ | リソース | メソッド | 主なクエリ |
|
|
235
235
|
| ------------------- | ----------------- | ---------------------------------- | ----------------------------------------- |
|
|
236
236
|
| `porters.partition` | Partition | `search` / `searchAll` | `requestType`(1=アクセス可能一覧・既定) |
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `
|
|
237
|
+
| `t.user` | User | `search` / `searchAll` / `current` | `requestType` / `userType` / `field` |
|
|
238
|
+
| `t.field` | Field(項目定義) | `search` / `searchAll` | `resource`(必須)/ `active` |
|
|
239
|
+
| `t.option` | Option(選択肢) | `search` | `alias` / `level` / `enabled` |
|
|
240
240
|
|
|
241
241
|
```ts
|
|
242
242
|
// アクセス可能な Partition(Company DB)を発見
|
|
243
243
|
const partitions = await porters.partition.search();
|
|
244
244
|
|
|
245
245
|
// 現在の API ユーザー(code_direct ではアプリ自身の User)=自己同定
|
|
246
|
-
const me = await
|
|
246
|
+
const me = await t.user.current();
|
|
247
247
|
|
|
248
248
|
// Job リソースの項目定義(U_/A_ カスタム項目を含む)を取得
|
|
249
|
-
const fields = await
|
|
249
|
+
const fields = await t.field.search({ resource: "job" });
|
|
250
250
|
|
|
251
251
|
// 選択肢マスタをフラットな配列で取得(親子は P_ParentId で復元)
|
|
252
|
-
const options = await
|
|
252
|
+
const options = await t.option.search({ alias: "Option.P_Gender" });
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
- `
|
|
255
|
+
- `t.option.search()` は入れ子の選択肢ツリーを**深さ優先でフラット化**して返します(全ノード・`P_ParentId`/`P_Order` で階層復元可)。`start` が無いため `searchAll` はありません。
|
|
256
256
|
- `porters.partition.current()` は提供しません(`request_type=0` は既定の `code_direct` 認証では 403 になるため)。一覧は `search()`(既定 `requestType: 1`)で取得します。
|
|
257
257
|
|
|
258
258
|
## 読み取り値の表現
|
|
@@ -272,7 +272,7 @@ PORTERS の Field Type に応じてデコードされます。
|
|
|
272
272
|
| 空・未設定 | `null` |
|
|
273
273
|
|
|
274
274
|
```ts
|
|
275
|
-
const c = await
|
|
275
|
+
const c = await t.candidate.get(10001);
|
|
276
276
|
c?.P_Id; // number
|
|
277
277
|
c?.P_Name; // string | null
|
|
278
278
|
c?.P_RegistrationDate; // "2026-01-02T03:04:05Z" | null
|
|
@@ -295,17 +295,17 @@ c?.P_Owner; // { P_Id, P_Name, ... } | null
|
|
|
295
295
|
|
|
296
296
|
```ts
|
|
297
297
|
// 作成(新規)
|
|
298
|
-
const newId = await
|
|
298
|
+
const newId = await t.candidate.create({
|
|
299
299
|
P_Owner: 5, // User 項目は id
|
|
300
300
|
P_Name: "鈴木 一郎",
|
|
301
301
|
P_Reading: "すずき いちろう",
|
|
302
302
|
});
|
|
303
303
|
|
|
304
304
|
// 更新
|
|
305
|
-
await
|
|
305
|
+
await t.candidate.update(newId, { P_Mail: "ichiro@example.com" });
|
|
306
306
|
|
|
307
307
|
// 選考プロセス(関連 id を指定)
|
|
308
|
-
await
|
|
308
|
+
await t.process.create({
|
|
309
309
|
P_Owner: 1,
|
|
310
310
|
P_Client: 100,
|
|
311
311
|
P_Recruiter: 200,
|
|
@@ -315,7 +315,7 @@ await porters.process.create({
|
|
|
315
315
|
});
|
|
316
316
|
|
|
317
317
|
// 一括作成/更新(200 件+サイズで自動分割・部分成功を返す)
|
|
318
|
-
const bulk = await
|
|
318
|
+
const bulk = await t.candidate.createMany([
|
|
319
319
|
{ P_Owner: 5, P_Name: "山田 太郎" },
|
|
320
320
|
{ P_Owner: 5, P_Name: "鈴木 花子" },
|
|
321
321
|
]);
|
|
@@ -331,7 +331,7 @@ if (bulk.hasFailures) console.warn(bulk.failed); // per-item は throw せず結
|
|
|
331
331
|
```ts
|
|
332
332
|
import { bytesToBase64 } from "@joymerrevent/porters-connect";
|
|
333
333
|
|
|
334
|
-
const id = await
|
|
334
|
+
const id = await t.attachment.create({
|
|
335
335
|
resource: 17, // 関連リソース種別コード(Resource List 参照)
|
|
336
336
|
resourceId: 10001, // 関連レコードの id
|
|
337
337
|
contentType: "application/pdf",
|
|
@@ -351,7 +351,7 @@ const id = await porters.attachment.create({
|
|
|
351
351
|
import { PortersError } from "@joymerrevent/porters-connect";
|
|
352
352
|
|
|
353
353
|
try {
|
|
354
|
-
await
|
|
354
|
+
await t.candidate.get(1);
|
|
355
355
|
} catch (e) {
|
|
356
356
|
if (e instanceof PortersError) {
|
|
357
357
|
e.category; // "auth" | "permission" | "notFound" | "conflict" | "network" | ...
|
|
@@ -365,7 +365,7 @@ try {
|
|
|
365
365
|
|
|
366
366
|
- トークン失効は内側で自動回復します。設定ミスは `PortersConfigError` で早期に落とします。
|
|
367
367
|
- **`Promise` を返す公開メソッドは同期 throw しません**([ADR-0046][adr46])。設定ミスも含め常に **reject** で届くので、
|
|
368
|
-
`
|
|
368
|
+
`t.candidate.search(q).catch(handler)` でも捕まえられます(`string` を返す `auth.authorizationUrl` や
|
|
369
369
|
コンストラクタなど、Promise を返さない API は同期 throw のままです)。
|
|
370
370
|
- 一時エラー・ネットワークは内蔵リトライ。非冪等な `create` はネットワーク不確実時に握り潰さず表面化します。
|
|
371
371
|
- レート制限超過時、PORTERS は判別可能なコードを返さず接続を切るため、`PortersNetworkError`(category `"network"`)として表面化します。
|
package/dist/index.d.ts
CHANGED
|
@@ -138,12 +138,12 @@ type UserRef = {
|
|
|
138
138
|
P_Mail: string | null;
|
|
139
139
|
};
|
|
140
140
|
type FieldValue = string | number | string[] | UserRef | null;
|
|
141
|
-
type DecodedValue<D extends DataType> = D extends "System[Id]" | "Number" | "System[Reference]" ? number : D extends "User" ? UserRef : D extends "Option" ? string[] : string;
|
|
141
|
+
type DecodedValue<D extends DataType | null> = D extends null ? string : D extends "System[Id]" | "Number" | "System[Reference]" ? number : D extends "User" ? UserRef : D extends "Option" ? string[] : string;
|
|
142
142
|
|
|
143
143
|
type WritableDataType = Exclude<DataType, "System[Id]" | "System[DateTime]">;
|
|
144
|
-
type WriteValueOf<D extends DataType> = D extends "User" | "System[Reference]" | "Number" ? number : D extends "Option" ? string[] : string;
|
|
144
|
+
type WriteValueOf<D extends DataType | null> = D extends null ? never : D extends "User" | "System[Reference]" | "Number" ? number : D extends "Option" ? string[] : string;
|
|
145
145
|
|
|
146
|
-
type FieldCatalog = Record<string, DataType>;
|
|
146
|
+
type FieldCatalog = Record<string, DataType | null>;
|
|
147
147
|
/**
|
|
148
148
|
* "No custom fields": the intersection-identity default for the generic catalog params (ADR-0023).
|
|
149
149
|
* A bare `{}` is flagged by `no-empty-object-type`; `Record<never, never>` is the same empty object,
|
|
@@ -208,8 +208,12 @@ type ReferenceCondition = {
|
|
|
208
208
|
or?: number[];
|
|
209
209
|
and?: number[];
|
|
210
210
|
};
|
|
211
|
-
/**
|
|
212
|
-
|
|
211
|
+
/**
|
|
212
|
+
* The condition-operator object a field of Data Type `D` accepts. A field PORTERS gives no Data
|
|
213
|
+
* Type (`null` — ADR-0056) falls through to the closing `never`, which is exactly right: the
|
|
214
|
+
* reference says such a field cannot appear in `condition` at all.
|
|
215
|
+
*/
|
|
216
|
+
type ConditionFor<D extends DataType | null> = D extends "System[Id]" ? IdCondition : D extends "Number" ? NumberCondition : D extends "DateTime" | "System[DateTime]" | "Date" | "Age" ? TemporalCondition : D extends "SinglelineText" | "MultilineText" | "Mail" | "Telephone" | "URL" ? TextCondition : D extends "Option" ? OptionCondition : D extends "User" | "System[Reference]" ? ReferenceCondition : never;
|
|
213
217
|
/**
|
|
214
218
|
* A typed search condition over a catalog: each field maps to the operator object its Data Type
|
|
215
219
|
* allows (ADR-0038 案1a). Multiple fields are AND-joined (reference). Unknown aliases / wrong
|
|
@@ -229,10 +233,15 @@ type OrderableKeys<F extends FieldCatalog> = {
|
|
|
229
233
|
*/
|
|
230
234
|
type Order<F extends FieldCatalog> = Array<Partial<Record<OrderableKeys<F>, "asc" | "desc">>>;
|
|
231
235
|
/**
|
|
232
|
-
* Which delete state to read.
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
+
* Which delete state to read. `existing` reads live data, `deleted`/`all` read deleted records —
|
|
237
|
+
* the only way to read deleted data, since there is no delete API. When `deleted`/`all`, condition
|
|
238
|
+
* is restricted to `P_Id` / `P_UpdateDate` / `P_UpdatedBy` and PORTERS auto-adds a "updated within
|
|
239
|
+
* 90 days" filter (`P_UpdateDate` = the delete time, `P_UpdatedBy` = the last editor).
|
|
240
|
+
*
|
|
241
|
+
* **Omitting the field is not the same as passing `existing`** (ADR-0057). Omitting defers to the
|
|
242
|
+
* API's own default (today: `existing`); passing `existing` states that you want live records only,
|
|
243
|
+
* and is sent as such. Both read live data now, but only the explicit form keeps doing so if PORTERS
|
|
244
|
+
* ever changes that default. Use `itemstate: "existing"` when live-only actually matters to you.
|
|
236
245
|
*/
|
|
237
246
|
type ItemState = "existing" | "deleted" | "all";
|
|
238
247
|
type SearchQuery<F extends FieldCatalog = FieldCatalog> = {
|
|
@@ -346,6 +355,7 @@ declare const FIELDS$8: {
|
|
|
346
355
|
readonly P_City: "SinglelineText";
|
|
347
356
|
readonly P_Street: "MultilineText";
|
|
348
357
|
readonly P_Zipcode: "SinglelineText";
|
|
358
|
+
readonly P_Deleted: null;
|
|
349
359
|
};
|
|
350
360
|
declare const REQUIRED_ON_CREATE$4: readonly ["P_Owner"];
|
|
351
361
|
/** A decoded Candidate: known `P_` fields, each requested field `value | null`. */
|
|
@@ -394,6 +404,7 @@ declare const FIELDS$7: {
|
|
|
394
404
|
readonly P_CapitalText: "SinglelineText";
|
|
395
405
|
readonly P_EmploymentType: "Option";
|
|
396
406
|
readonly P_ExpectedAgeReason: "Option";
|
|
407
|
+
readonly P_Deleted: null;
|
|
397
408
|
};
|
|
398
409
|
declare const REQUIRED_ON_CREATE$3: readonly ["P_Owner", "P_Client", "P_Recruiter"];
|
|
399
410
|
/** A decoded Job: known `P_` fields, each requested field `value | null`. */
|
|
@@ -426,6 +437,7 @@ declare const FIELDS$6: {
|
|
|
426
437
|
readonly P_Zipcode: "SinglelineText";
|
|
427
438
|
readonly P_Telephone: "Telephone";
|
|
428
439
|
readonly P_Fax: "Telephone";
|
|
440
|
+
readonly P_Deleted: null;
|
|
429
441
|
};
|
|
430
442
|
declare const REQUIRED_ON_CREATE$2: readonly ["P_Owner"];
|
|
431
443
|
/** A decoded Client (company): known `P_` fields, each requested field `value | null`. */
|
|
@@ -458,6 +470,7 @@ declare const FIELDS$5: {
|
|
|
458
470
|
readonly P_CloseReason: "Option";
|
|
459
471
|
readonly P_ExpectedSalesAmount: "Number";
|
|
460
472
|
readonly P_ExpectedClosingDate: "Date";
|
|
473
|
+
readonly P_Deleted: null;
|
|
461
474
|
};
|
|
462
475
|
declare const REQUIRED_ON_CREATE$1: readonly ["P_Owner", "P_Client", "P_Recruiter", "P_Job", "P_Candidate", "P_Resume"];
|
|
463
476
|
/** A decoded Process (a Candidate's progress through a Job): known `P_` fields, each
|
|
@@ -503,6 +516,7 @@ declare const FIELDS$4: {
|
|
|
503
516
|
readonly P_ExpectSalary: "Number";
|
|
504
517
|
readonly P_DesiredHourlyRate: "Number";
|
|
505
518
|
readonly P_HourlyRate: "Number";
|
|
519
|
+
readonly P_Deleted: null;
|
|
506
520
|
};
|
|
507
521
|
declare const REQUIRED_ON_CREATE: readonly ["P_Owner", "P_Candidate"];
|
|
508
522
|
/** A decoded Resume (a Candidate's CV / profile): known `P_` fields, each requested field
|
|
@@ -779,12 +793,6 @@ type PortersClientOptions<C extends DeclaredCatalogs = EmptyCatalog> = {
|
|
|
779
793
|
appId?: string;
|
|
780
794
|
appSecret?: string;
|
|
781
795
|
scopes?: Scope[];
|
|
782
|
-
/**
|
|
783
|
-
* Default partition (Company DB) for every call. For multi-tenant routing, bind a partition
|
|
784
|
-
* per call with {@link PortersClient.tenant} (ADR-0040 / F-3); for a fully separated per-partition
|
|
785
|
-
* token, construct a dedicated client per tenant instead (ADR-0008 案3).
|
|
786
|
-
*/
|
|
787
|
-
partition?: PartitionId;
|
|
788
796
|
/** Custom auth strategy; defaults to the transparent code_direct strategy. */
|
|
789
797
|
auth?: TokenProvider;
|
|
790
798
|
/** Token persistence; defaults to in-memory. */
|
|
@@ -800,8 +808,10 @@ type PortersClientOptions<C extends DeclaredCatalogs = EmptyCatalog> = {
|
|
|
800
808
|
};
|
|
801
809
|
/**
|
|
802
810
|
* The partition-bound resource accessors returned by {@link PortersClient.tenant} (ADR-0040 / F-3).
|
|
803
|
-
*
|
|
804
|
-
*
|
|
811
|
+
* **This is the only way to reach a partition-scoped resource** (ADR-0055): PORTERS requires
|
|
812
|
+
* `partition` on every one of these calls, so the API makes you supply it exactly once, explicitly.
|
|
813
|
+
* `auth` (App-level), the `partition` master (discovery — partition-less), and `tenant` itself
|
|
814
|
+
* (no nesting) are deliberately absent: none of them takes a partition.
|
|
805
815
|
*/
|
|
806
816
|
type TenantScope<C extends DeclaredCatalogs = EmptyCatalog> = {
|
|
807
817
|
readonly candidate: CandidateResource<CustomFor<C, "candidate">>;
|
|
@@ -815,36 +825,41 @@ type TenantScope<C extends DeclaredCatalogs = EmptyCatalog> = {
|
|
|
815
825
|
readonly option: OptionResource;
|
|
816
826
|
};
|
|
817
827
|
/**
|
|
818
|
-
* Entry point of the library. Wires the default transport / auth / throttle /
|
|
819
|
-
*
|
|
820
|
-
*
|
|
828
|
+
* Entry point of the library. Wires the default transport / auth / throttle / requester and exposes
|
|
829
|
+
* the **App-level** surface: `auth`, the `partition` master (discovery), and {@link PortersClient.tenant}.
|
|
830
|
+
*
|
|
831
|
+
* Everything that PORTERS scopes to a partition (Company DB) lives behind `tenant(id)` — see
|
|
832
|
+
* {@link TenantScope}. The client holds no default partition (ADR-0055): a partition is bound
|
|
833
|
+
* explicitly, exactly once, so "unbound" is not a state this API can be in.
|
|
834
|
+
*
|
|
835
|
+
* @example
|
|
836
|
+
* const porters = new PortersClient({ host, appId, appSecret });
|
|
837
|
+
* await porters.auth.ensureAuthenticated(); // App-level
|
|
838
|
+
* const t = porters.tenant(123); // bind the partition once
|
|
839
|
+
* const page = await t.candidate.search();
|
|
821
840
|
*/
|
|
822
841
|
declare class PortersClient<C extends DeclaredCatalogs = EmptyCatalog> {
|
|
823
842
|
#private;
|
|
824
|
-
readonly candidate: CandidateResource<CustomFor<C, "candidate">>;
|
|
825
|
-
readonly job: JobResource<CustomFor<C, "job">>;
|
|
826
|
-
readonly client: ClientResource<CustomFor<C, "client">>;
|
|
827
|
-
readonly process: ProcessResource<CustomFor<C, "process">>;
|
|
828
|
-
readonly resume: ResumeResource<CustomFor<C, "resume">>;
|
|
829
|
-
readonly attachment: AttachmentResource;
|
|
830
843
|
/** OAuth surface: initial browser grant, token warm-up/inspection, local revoke (ADR-0007/0034). */
|
|
831
844
|
readonly auth: AuthApi;
|
|
832
|
-
/**
|
|
845
|
+
/**
|
|
846
|
+
* Master Read: the partitions this App can reach (ADR-0021/0022). Takes no `partition` itself —
|
|
847
|
+
* it is how you *discover* the ids to pass to {@link PortersClient.tenant}.
|
|
848
|
+
*/
|
|
833
849
|
readonly partition: PartitionResource;
|
|
834
|
-
/** Master Read: users, plus `current()` self-identification (ADR-0021/0022). */
|
|
835
|
-
readonly user: UserResource;
|
|
836
|
-
/** Master Read: a resource's field catalog (ADR-0021/0022). */
|
|
837
|
-
readonly field: FieldResource;
|
|
838
|
-
/** Master Read: a tenant's choice (option) master (ADR-0021/0022). */
|
|
839
|
-
readonly option: OptionResource;
|
|
840
850
|
/**
|
|
841
|
-
* Bind a
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
851
|
+
* Bind a partition (Company DB) and get the accessors that route through it (ADR-0040 F-3).
|
|
852
|
+
* **Single-tenant apps use this too** — it is the only path to a partition-scoped resource
|
|
853
|
+
* (ADR-0055). Hold the scope once and use it like a client:
|
|
854
|
+
*
|
|
855
|
+
* ```ts
|
|
856
|
+
* const t = porters.tenant(123);
|
|
857
|
+
* await t.candidate.search();
|
|
858
|
+
* ```
|
|
859
|
+
*
|
|
860
|
+
* `auth` (App-level), the `partition` master (discovery — takes no partition), and `tenant`
|
|
861
|
+
* itself (no nesting) are intentionally absent from the returned scope. For a fully separated
|
|
862
|
+
* per-partition token, construct a dedicated {@link PortersClient} per tenant (ADR-0008 案3).
|
|
848
863
|
*/
|
|
849
864
|
readonly tenant: (id: PartitionId) => TenantScope<C>;
|
|
850
865
|
constructor(options: PortersClientOptions<C>);
|
package/dist/index.js
CHANGED
|
@@ -740,7 +740,7 @@ var encodeItem = (prefix, fields, item) => {
|
|
|
740
740
|
for (const [alias, value] of Object.entries(item)) {
|
|
741
741
|
if (value === null || value === void 0) continue;
|
|
742
742
|
const type = fields.get(alias);
|
|
743
|
-
const inner = type === void 0 ? scalar(value) : encodeField(type, value);
|
|
743
|
+
const inner = type === void 0 || type === null ? scalar(value) : encodeField(type, value);
|
|
744
744
|
parts.push(`<${prefix}.${alias}>${inner}</${prefix}.${alias}>`);
|
|
745
745
|
}
|
|
746
746
|
return parts.join("");
|
|
@@ -786,6 +786,7 @@ var decodeReference = (raw) => {
|
|
|
786
786
|
};
|
|
787
787
|
var decodeField = (type, raw) => {
|
|
788
788
|
if (raw === "" || raw === void 0 || raw === null) return null;
|
|
789
|
+
if (type === null) return asString(raw) ?? null;
|
|
789
790
|
switch (type) {
|
|
790
791
|
case "System[Id]":
|
|
791
792
|
case "Number": {
|
|
@@ -832,7 +833,7 @@ var decoderFor = (fields) => {
|
|
|
832
833
|
for (const [key, raw] of Object.entries(item)) {
|
|
833
834
|
const alias = bareAlias(key);
|
|
834
835
|
const type = fieldMap.get(alias);
|
|
835
|
-
out[alias] = type
|
|
836
|
+
out[alias] = type === void 0 ? typeof raw === "string" ? raw : null : decodeField(type, raw);
|
|
836
837
|
}
|
|
837
838
|
return out;
|
|
838
839
|
};
|
|
@@ -946,9 +947,7 @@ var appendReadQuery = (p, q, ctx) => {
|
|
|
946
947
|
}
|
|
947
948
|
p.set("keywords", kw);
|
|
948
949
|
}
|
|
949
|
-
if (q.itemstate !== void 0
|
|
950
|
-
p.set("itemstate", q.itemstate);
|
|
951
|
-
}
|
|
950
|
+
if (q.itemstate !== void 0) p.set("itemstate", q.itemstate);
|
|
952
951
|
};
|
|
953
952
|
|
|
954
953
|
// src/resources/bulk-write.ts
|
|
@@ -1069,7 +1068,9 @@ var buildWriteUrl = (accessPoint, partition, path) => apiUrl(
|
|
|
1069
1068
|
new URLSearchParams({ partition: String(partition) })
|
|
1070
1069
|
);
|
|
1071
1070
|
var createResource = (config, deps) => {
|
|
1072
|
-
const fieldMap = new Map(
|
|
1071
|
+
const fieldMap = new Map(
|
|
1072
|
+
Object.entries(config.fields)
|
|
1073
|
+
);
|
|
1073
1074
|
const decode = decoderFor(config.fields);
|
|
1074
1075
|
const defaultFields = defaultFieldList(config.prefix, config.fields);
|
|
1075
1076
|
const readUrl = (q) => buildReadUrl(deps.accessPoint, deps.partition, config.path, q, {
|
|
@@ -1151,7 +1152,12 @@ var FIELDS = {
|
|
|
1151
1152
|
P_Prefecture: "SinglelineText",
|
|
1152
1153
|
P_City: "SinglelineText",
|
|
1153
1154
|
P_Street: "MultilineText",
|
|
1154
|
-
P_Zipcode: "SinglelineText"
|
|
1155
|
+
P_Zipcode: "SinglelineText",
|
|
1156
|
+
// 削除状態("0" 生存 / "1" 削除済み)。PORTERS はこの項目に Data Type を与えていない
|
|
1157
|
+
// (reference の Field Type / Data Type とも「ー」)ので `null`=型が無いことを記録する(ADR-0056)。
|
|
1158
|
+
// 「未設定」でも「不明」でもない。null は condition / order / Write の型から自動的に外れる=
|
|
1159
|
+
// reference の「Read の field でのみ指定可」がそのまま型で守られる。
|
|
1160
|
+
P_Deleted: null
|
|
1155
1161
|
};
|
|
1156
1162
|
var REQUIRED_ON_CREATE = [
|
|
1157
1163
|
"P_Owner"
|
|
@@ -1206,7 +1212,10 @@ var FIELDS2 = {
|
|
|
1206
1212
|
P_EstablishmentDateText: "SinglelineText",
|
|
1207
1213
|
P_CapitalText: "SinglelineText",
|
|
1208
1214
|
P_EmploymentType: "Option",
|
|
1209
|
-
P_ExpectedAgeReason: "Option"
|
|
1215
|
+
P_ExpectedAgeReason: "Option",
|
|
1216
|
+
// 削除状態("0" / "1")。PORTERS が Data Type を与えていない項目=`null`(ADR-0056。
|
|
1217
|
+
// 詳細は candidate.ts のコメント)。Read の field でのみ指定でき、型もそう振る舞う。
|
|
1218
|
+
P_Deleted: null
|
|
1210
1219
|
};
|
|
1211
1220
|
var REQUIRED_ON_CREATE2 = [
|
|
1212
1221
|
"P_Owner",
|
|
@@ -1246,7 +1255,10 @@ var FIELDS3 = {
|
|
|
1246
1255
|
P_Street: "MultilineText",
|
|
1247
1256
|
P_Zipcode: "SinglelineText",
|
|
1248
1257
|
P_Telephone: "Telephone",
|
|
1249
|
-
P_Fax: "Telephone"
|
|
1258
|
+
P_Fax: "Telephone",
|
|
1259
|
+
// 削除状態("0" / "1")。PORTERS が Data Type を与えていない項目=`null`(ADR-0056。
|
|
1260
|
+
// 詳細は candidate.ts のコメント)。Read の field でのみ指定でき、型もそう振る舞う。
|
|
1261
|
+
P_Deleted: null
|
|
1250
1262
|
};
|
|
1251
1263
|
var REQUIRED_ON_CREATE3 = [
|
|
1252
1264
|
"P_Owner"
|
|
@@ -1284,7 +1296,10 @@ var FIELDS4 = {
|
|
|
1284
1296
|
P_Close: "Option",
|
|
1285
1297
|
P_CloseReason: "Option",
|
|
1286
1298
|
P_ExpectedSalesAmount: "Number",
|
|
1287
|
-
P_ExpectedClosingDate: "Date"
|
|
1299
|
+
P_ExpectedClosingDate: "Date",
|
|
1300
|
+
// 削除状態("0" / "1")。PORTERS が Data Type を与えていない項目=`null`(ADR-0056。
|
|
1301
|
+
// 詳細は candidate.ts のコメント)。Read の field でのみ指定でき、型もそう振る舞う。
|
|
1302
|
+
P_Deleted: null
|
|
1288
1303
|
};
|
|
1289
1304
|
var REQUIRED_ON_CREATE4 = [
|
|
1290
1305
|
"P_Owner",
|
|
@@ -1339,7 +1354,10 @@ var FIELDS5 = {
|
|
|
1339
1354
|
P_ExpectCondition: "MultilineText",
|
|
1340
1355
|
P_ExpectSalary: "Number",
|
|
1341
1356
|
P_DesiredHourlyRate: "Number",
|
|
1342
|
-
P_HourlyRate: "Number"
|
|
1357
|
+
P_HourlyRate: "Number",
|
|
1358
|
+
// 削除状態("0" / "1")。PORTERS が Data Type を与えていない項目=`null`(ADR-0056。
|
|
1359
|
+
// 詳細は candidate.ts のコメント)。Read の field でのみ指定でき、型もそう振る舞う。
|
|
1360
|
+
P_Deleted: null
|
|
1343
1361
|
};
|
|
1344
1362
|
var REQUIRED_ON_CREATE5 = [
|
|
1345
1363
|
"P_Owner",
|
|
@@ -1637,30 +1655,26 @@ var createOptionResource = (deps) => {
|
|
|
1637
1655
|
|
|
1638
1656
|
// src/client.ts
|
|
1639
1657
|
var PortersClient = class {
|
|
1640
|
-
candidate;
|
|
1641
|
-
job;
|
|
1642
|
-
client;
|
|
1643
|
-
process;
|
|
1644
|
-
resume;
|
|
1645
|
-
attachment;
|
|
1646
1658
|
/** OAuth surface: initial browser grant, token warm-up/inspection, local revoke (ADR-0007/0034). */
|
|
1647
1659
|
auth;
|
|
1648
|
-
/**
|
|
1660
|
+
/**
|
|
1661
|
+
* Master Read: the partitions this App can reach (ADR-0021/0022). Takes no `partition` itself —
|
|
1662
|
+
* it is how you *discover* the ids to pass to {@link PortersClient.tenant}.
|
|
1663
|
+
*/
|
|
1649
1664
|
partition;
|
|
1650
|
-
/** Master Read: users, plus `current()` self-identification (ADR-0021/0022). */
|
|
1651
|
-
user;
|
|
1652
|
-
/** Master Read: a resource's field catalog (ADR-0021/0022). */
|
|
1653
|
-
field;
|
|
1654
|
-
/** Master Read: a tenant's choice (option) master (ADR-0021/0022). */
|
|
1655
|
-
option;
|
|
1656
1665
|
/**
|
|
1657
|
-
* Bind a
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1662
|
-
*
|
|
1663
|
-
*
|
|
1666
|
+
* Bind a partition (Company DB) and get the accessors that route through it (ADR-0040 F-3).
|
|
1667
|
+
* **Single-tenant apps use this too** — it is the only path to a partition-scoped resource
|
|
1668
|
+
* (ADR-0055). Hold the scope once and use it like a client:
|
|
1669
|
+
*
|
|
1670
|
+
* ```ts
|
|
1671
|
+
* const t = porters.tenant(123);
|
|
1672
|
+
* await t.candidate.search();
|
|
1673
|
+
* ```
|
|
1674
|
+
*
|
|
1675
|
+
* `auth` (App-level), the `partition` master (discovery — takes no partition), and `tenant`
|
|
1676
|
+
* itself (no nesting) are intentionally absent from the returned scope. For a fully separated
|
|
1677
|
+
* per-partition token, construct a dedicated {@link PortersClient} per tenant (ADR-0008 案3).
|
|
1664
1678
|
*/
|
|
1665
1679
|
tenant;
|
|
1666
1680
|
#accessPoint;
|
|
@@ -1719,16 +1733,6 @@ var PortersClient = class {
|
|
|
1719
1733
|
};
|
|
1720
1734
|
};
|
|
1721
1735
|
this.tenant = buildScope;
|
|
1722
|
-
const root = buildScope(options.partition ?? 0);
|
|
1723
|
-
this.candidate = root.candidate;
|
|
1724
|
-
this.job = root.job;
|
|
1725
|
-
this.client = root.client;
|
|
1726
|
-
this.process = root.process;
|
|
1727
|
-
this.resume = root.resume;
|
|
1728
|
-
this.attachment = root.attachment;
|
|
1729
|
-
this.user = root.user;
|
|
1730
|
-
this.field = root.field;
|
|
1731
|
-
this.option = root.option;
|
|
1732
1736
|
this.partition = createPartitionResource({ requester, accessPoint });
|
|
1733
1737
|
}
|
|
1734
1738
|
/** The configured API host. */
|