@joymerrevent/porters-connect 0.8.0 → 0.9.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,48 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.9.0] - 2026-08-20
9
+
10
+ **Candidate でメモ・住所詳細が扱えるようになった版**です。標準項目でありながらカタログから漏れていた
11
+ 4 項目を追加し、同じ取りこぼしが二度と起きないよう reference との突合を CI で検査するようにしました。
12
+ 併せて `defineFields` と Read クエリの**利用ガイドを新設**しています。
13
+
14
+ ### Added
15
+
16
+ - **Candidate に標準 4 項目**(`P_Memo`(メモ)/`P_Street`(住所詳細)/`P_Fax`(FAX)/`P_PhaseMemo`(フェーズメモ))。
17
+ カタログが型の真実源([ADR-0019][adr19])なので、これまで **`Candidate` 型にも `CandidateCreateInput` /
18
+ `CandidateUpdateInput` にも現れず**、`candidate.create({ P_Memo: "…" })` は型エラーでした。
19
+ `field` 省略時の既定 field([ADR-0020][adr20])にも含まれないため **Read でも取得されません**でした。
20
+ 同じ 4 種は Client には最初から載っており、リソース間で非一貫でした。
21
+ - `field` を省略した Read は**既定 field が 4 つ増えるぶん URL が長くなります**(Candidate で約 100 文字)。
22
+ - **公開ジェネリクスの制約型を export**(`DeclaredCatalogs` / `CustomFor` / `CustomFieldResource`)。
23
+ `PortersClient<C>` / `TenantScope<C>` は公開型なのに制約側が非公開で、
24
+ クライアントを引数に取るヘルパーの型を**名前で書けません**でした(`typeof porters` での回避のみ)。
25
+ 型の追加のみで、既存コードへの影響はありません。
26
+ - **利用ガイドを 2 本新設**([ADR-0035][adr35] の型)。
27
+ - [カスタム項目ガイド][custom-fields-guide] — `defineFields` の宣言、**宣言しないとどうなるか**
28
+ (型が付かず `field` 省略時に取得されない)、宣言できる 11 の Data Type、
29
+ Field Read でテナントの項目を調べる手順、複数テナントでの使い分け。
30
+ - [Read クエリ ガイド][read-query-guide] — `field` の 3 通りの意味、**Data Type ごとの演算子一覧**、
31
+ 削除済み Read の制約(condition は 3 項目・90 日以内)、送信前に落ちるものの一覧。
32
+
33
+ ### Changed
34
+
35
+ - **Read の `count` が範囲外なら送信前に落ちます**。`count` は PORTERS 上 **1〜200**(既定 10)ですが、
36
+ これまで範囲外の値をそのまま送っており、不透明なサーバー応答に倒れていました。
37
+ 今後は `PortersConfigError`(`category: "config"` + `searchAll()` を案内する `hint`)で**リクエストの前に**弾きます。
38
+ 整数でない値(`1.5` など)も同様です。
39
+ - keywords の 100 文字・itemstate の condition 制限・リクエスト長 ~15000 字と同じ
40
+ 「早く・明確に落とす」系列に揃いました。データ系・マスタ系・Attachment の**すべての Read 経路**に効きます。
41
+ - `count` を省略した場合の挙動は変わりません(API 既定に委ねます)。
42
+
43
+ ### Fixed
44
+
45
+ - **reference と静的カタログの突合を CI で検査**するようになりました。値を持つ標準項目の取りこぼし・
46
+ カタログ側の幻の項目・Field Type → Data Type の取り違えを検出します。
47
+ 上記 Candidate の欠落は **0.1.0 から 12 版・563 テストをすべて素通り**していたため、
48
+ 人の目ではなく仕組みで守ります。
49
+
8
50
  ## [0.8.0] - 2026-08-14
9
51
 
10
52
  **「0 件」と「届いていない」を区別できるようにした版**です。HTTP 200 を返す中間装置
@@ -241,9 +283,15 @@
241
283
  [adr51]: docs/adr/0051-read-envelope-identification.md
242
284
  [adr47]: docs/adr/0047-access-point-scheme.md
243
285
  [oauth-guide]: docs/guide/oauth.md
286
+ [adr19]: docs/adr/0019-static-resource-types.md
287
+ [adr20]: docs/adr/0020-read-field-default.md
288
+ [adr35]: docs/adr/0035-usage-documentation-structure.md
289
+ [custom-fields-guide]: docs/guide/custom-fields.md
290
+ [read-query-guide]: docs/guide/read-query.md
244
291
  [kac]: https://keepachangelog.com/en/1.1.0/
245
292
  [semver]: https://semver.org/
246
- [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.8.0...HEAD
293
+ [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.9.0...HEAD
294
+ [0.9.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.8.0...v0.9.0
247
295
  [0.8.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.7.0...v0.8.0
248
296
  [0.7.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.6.2...v0.7.0
249
297
  [0.6.2]: https://github.com/Joymerrevent/porters-connect/compare/v0.6.1...v0.6.2
package/README.md CHANGED
@@ -192,10 +192,40 @@ await porters.auth.exchangeAuthorizationCode(code);
192
192
  - `order`:並び順。`[{ 項目: "asc" | "desc" }]`(数値・日時・System 型のみ)。
193
193
  - `keywords`:テキスト項目のキーワード AND 検索(`string[]`・カンマ込み **100 文字まで**)。
194
194
  - `itemstate`:`"existing"`(既定)/ `"deleted"` / `"all"`。削除済みデータの取得。
195
- - `count`(1–200・既定 10)、`start`(0 始まり)。
195
+ - `count`(1–200・既定 10)、`start`(0 始まり)。**範囲外の `count` は送信前に `PortersConfigError`** で落ちます。
196
196
 
197
197
  > **削除 API はありません**(PORTERS 仕様)。`delete()` メソッドは提供しません。削除済みは `itemstate: "deleted"` で読みます
198
198
  > (`condition` は `P_Id` / `P_UpdateDate` / `P_UpdatedBy` に限られ、更新日は 90 日以内)。
199
+ >
200
+ > `field` の 3 通りの意味(省略=全項目 / `[]`=主キーのみ / 明示)、Data Type ごとの演算子一覧、
201
+ > 削除済み Read の制約、送信前に落ちる条件は [Read クエリ ガイド][read-query-guide] にまとめています。
202
+
203
+ ### カスタム項目(`U_` / `A_`)
204
+
205
+ テナント固有のカスタム項目は `defineFields` で宣言すると、**読み書きの型に現れ**、Data Type どおりに変換されます。
206
+
207
+ ```ts
208
+ import { PortersClient, defineFields } from "@joymerrevent/porters-connect";
209
+
210
+ const fields = defineFields({
211
+ candidate: (f) => ({ U_score: f.number(), U_source: f.option() }),
212
+ });
213
+ const porters = new PortersClient({
214
+ host,
215
+ appId,
216
+ appSecret,
217
+ partition,
218
+ fields,
219
+ });
220
+
221
+ const one = await porters.candidate.get(10001);
222
+ one?.U_score; // number | null | undefined
223
+ ```
224
+
225
+ 宣言しなくても読み書きはできますが(生の文字列として通ります)、**型が付かず、`field` 省略時に取得もされません**。
226
+
227
+ > 宣言できる型の一覧、テナントの項目を Field Read で調べる方法、複数テナントでの扱いは
228
+ > [カスタム項目 ガイド][custom-fields-guide] にまとめています。
199
229
 
200
230
  ### マスタ Read(読み取り専用)
201
231
 
@@ -407,6 +437,8 @@ try {
407
437
  [auth-flow]: ./docs/reference/authentication-api/README.md
408
438
  [oauth-guide]: ./docs/guide/oauth.md
409
439
  [error-handling]: ./docs/guide/error-handling.md
440
+ [custom-fields-guide]: ./docs/guide/custom-fields.md
441
+ [read-query-guide]: ./docs/guide/read-query.md
410
442
  [multi-tenancy]: ./docs/guide/multi-tenancy.md
411
443
  [bulk-write]: ./docs/guide/bulk-write.md
412
444
  [sandbox]: ./examples/offline-sandbox.ts
package/dist/index.d.ts CHANGED
@@ -332,15 +332,19 @@ declare const FIELDS$8: {
332
332
  readonly P_UpdatedBy: "User";
333
333
  readonly P_Phase: "Option";
334
334
  readonly P_PhaseDate: "DateTime";
335
+ readonly P_PhaseMemo: "MultilineText";
335
336
  readonly P_Name: "SinglelineText";
336
337
  readonly P_Reading: "SinglelineText";
338
+ readonly P_Memo: "MultilineText";
337
339
  readonly P_Mail: "Mail";
338
340
  readonly P_MobileMail: "Mail";
339
341
  readonly P_Telephone: "Telephone";
340
342
  readonly P_Mobile: "Telephone";
343
+ readonly P_Fax: "Telephone";
341
344
  readonly P_Country: "SinglelineText";
342
345
  readonly P_Prefecture: "SinglelineText";
343
346
  readonly P_City: "SinglelineText";
347
+ readonly P_Street: "MultilineText";
344
348
  readonly P_Zipcode: "SinglelineText";
345
349
  };
346
350
  declare const REQUIRED_ON_CREATE$4: readonly ["P_Owner"];
@@ -901,4 +905,4 @@ declare const bytesToBase64: (bytes: Uint8Array) => string;
901
905
  /** Decode a Base64 string back to raw bytes. */
902
906
  declare const base64ToBytes: (b64: string) => Uint8Array;
903
907
 
904
- export { type Attachment, type AttachmentCreate, type AttachmentPage, type AttachmentResource, type AttachmentSearchQuery, type AttachmentUpdate, type AuthApi, type AuthorizationUrlOptions, type BulkWriteResult, type BulkWriteResultItem, type Candidate, type CandidateCreateInput, type CandidatePage, type CandidateResource, type CandidateSearchQuery, type CandidateUpdateInput, type Client, type ClientCreateInput, type ClientPage, type ClientResource, type ClientSearchQuery, type ClientUpdateInput, type Condition, type CustomDataType, type DefinedFields, type ErrorCategory, type Field, type FieldBuilder, type FieldDecls, type FieldDef, type FieldPage, type FieldResource, type FieldSearchQuery, type FieldValue, type GetAccessTokenOptions, type ItemState, type Job, type JobCreateInput, type JobPage, type JobResource, type JobSearchQuery, type JobUpdateInput, type MockHandler, type MockReply, type MockTransportOptions, type Option, type OptionResource, type OptionSearchQuery, type Order, type Partition, type PartitionId, type PartitionPage, type PartitionResource, type PartitionSearchQuery, PortersAuthError, PortersClient, type PortersClientOptions, PortersConfigError, PortersError, type PortersErrorContext, type PortersErrorOptions, PortersNetworkError, PortersResourceError, type Process, type ProcessCreateInput, type ProcessPage, type ProcessResource, type ProcessSearchQuery, type ProcessUpdateInput, type ResourceType, type Resume, type ResumeCreateInput, type ResumePage, type ResumeResource, type ResumeSearchQuery, type ResumeUpdateInput, type RevokeUrlOptions, type Scheme, type Scope, type SearchQuery, type StoredTokens, type TenantScope, type TokenProvider, type TokenStore, type Transport, type TransportRequest, type TransportResponse, type User, type UserPage, type UserRef, type UserResource, type UserSearchQuery, base64ToBytes, bytesToBase64, createMockTransport, defineFields };
908
+ export { type Attachment, type AttachmentCreate, type AttachmentPage, type AttachmentResource, type AttachmentSearchQuery, type AttachmentUpdate, type AuthApi, type AuthorizationUrlOptions, type BulkWriteResult, type BulkWriteResultItem, type Candidate, type CandidateCreateInput, type CandidatePage, type CandidateResource, type CandidateSearchQuery, type CandidateUpdateInput, type Client, type ClientCreateInput, type ClientPage, type ClientResource, type ClientSearchQuery, type ClientUpdateInput, type Condition, type CustomDataType, type CustomFieldResource, type CustomFor, type DeclaredCatalogs, type DefinedFields, type ErrorCategory, type Field, type FieldBuilder, type FieldDecls, type FieldDef, type FieldPage, type FieldResource, type FieldSearchQuery, type FieldValue, type GetAccessTokenOptions, type ItemState, type Job, type JobCreateInput, type JobPage, type JobResource, type JobSearchQuery, type JobUpdateInput, type MockHandler, type MockReply, type MockTransportOptions, type Option, type OptionResource, type OptionSearchQuery, type Order, type Partition, type PartitionId, type PartitionPage, type PartitionResource, type PartitionSearchQuery, PortersAuthError, PortersClient, type PortersClientOptions, PortersConfigError, PortersError, type PortersErrorContext, type PortersErrorOptions, PortersNetworkError, PortersResourceError, type Process, type ProcessCreateInput, type ProcessPage, type ProcessResource, type ProcessSearchQuery, type ProcessUpdateInput, type ResourceType, type Resume, type ResumeCreateInput, type ResumePage, type ResumeResource, type ResumeSearchQuery, type ResumeUpdateInput, type RevokeUrlOptions, type Scheme, type Scope, type SearchQuery, type StoredTokens, type TenantScope, type TokenProvider, type TokenStore, type Transport, type TransportRequest, type TransportResponse, type User, type UserPage, type UserRef, type UserResource, type UserSearchQuery, base64ToBytes, bytesToBase64, createMockTransport, defineFields };
package/dist/index.js CHANGED
@@ -846,7 +846,24 @@ var runRead = (requester, resource, url, decode) => requester.request({ method:
846
846
  start: page.start
847
847
  };
848
848
  });
849
- var PAGE_SIZE = 200;
849
+ var MIN_READ_COUNT = 1;
850
+ var MAX_READ_COUNT = 200;
851
+ var PAGE_SIZE = MAX_READ_COUNT;
852
+ var appendPaging = (p, count, start) => {
853
+ if (count !== void 0) {
854
+ if (!Number.isInteger(count) || count < MIN_READ_COUNT || count > MAX_READ_COUNT) {
855
+ throw new PortersConfigError(
856
+ `count must be an integer between ${MIN_READ_COUNT} and ${MAX_READ_COUNT}, got ${count}`,
857
+ {
858
+ category: "config",
859
+ hint: `PORTERS returns 1\u2013${MAX_READ_COUNT} records per Read. Use searchAll() to walk every page instead of raising count.`
860
+ }
861
+ );
862
+ }
863
+ p.set("count", String(count));
864
+ }
865
+ if (start !== void 0) p.set("start", String(start));
866
+ };
850
867
  var paginate = async function* (fetchPage) {
851
868
  let start = 0;
852
869
  for (; ; ) {
@@ -1043,8 +1060,7 @@ var buildReadUrl = (accessPoint, partition, path, q, ctx) => {
1043
1060
  p.set("partition", String(partition));
1044
1061
  if (q.field && q.field.length > 0) p.set("field", q.field.join(","));
1045
1062
  appendReadQuery(p, q, ctx);
1046
- if (q.count !== void 0) p.set("count", String(q.count));
1047
- if (q.start !== void 0) p.set("start", String(q.start));
1063
+ appendPaging(p, q.count, q.start);
1048
1064
  return apiUrl(accessPoint, path, p);
1049
1065
  };
1050
1066
  var buildWriteUrl = (accessPoint, partition, path) => apiUrl(
@@ -1122,15 +1138,19 @@ var FIELDS = {
1122
1138
  P_UpdatedBy: "User",
1123
1139
  P_Phase: "Option",
1124
1140
  P_PhaseDate: "DateTime",
1141
+ P_PhaseMemo: "MultilineText",
1125
1142
  P_Name: "SinglelineText",
1126
1143
  P_Reading: "SinglelineText",
1144
+ P_Memo: "MultilineText",
1127
1145
  P_Mail: "Mail",
1128
1146
  P_MobileMail: "Mail",
1129
1147
  P_Telephone: "Telephone",
1130
1148
  P_Mobile: "Telephone",
1149
+ P_Fax: "Telephone",
1131
1150
  P_Country: "SinglelineText",
1132
1151
  P_Prefecture: "SinglelineText",
1133
1152
  P_City: "SinglelineText",
1153
+ P_Street: "MultilineText",
1134
1154
  P_Zipcode: "SinglelineText"
1135
1155
  };
1136
1156
  var REQUIRED_ON_CREATE = [
@@ -1365,8 +1385,7 @@ var buildAttachmentReadUrl = (accessPoint, partition, q) => {
1365
1385
  const conds = Object.entries(q.condition).map(([k, v]) => `${k}=${v}`);
1366
1386
  if (conds.length > 0) p.set("condition", conds.join(","));
1367
1387
  }
1368
- if (q.count !== void 0) p.set("count", String(q.count));
1369
- if (q.start !== void 0) p.set("start", String(q.start));
1388
+ appendPaging(p, q.count, q.start);
1370
1389
  return apiUrl(accessPoint, "attachment", p);
1371
1390
  };
1372
1391
  var numOrNull = (v) => {
@@ -1461,8 +1480,7 @@ var PARTITION_DESCRIPTOR = {
1461
1480
  var buildUrl = (accessPoint, q) => {
1462
1481
  const p = new URLSearchParams();
1463
1482
  p.set("request_type", String(q.requestType ?? 1));
1464
- if (q.count !== void 0) p.set("count", String(q.count));
1465
- if (q.start !== void 0) p.set("start", String(q.start));
1483
+ appendPaging(p, q.count, q.start);
1466
1484
  return apiUrl(accessPoint, "partition", p);
1467
1485
  };
1468
1486
  var createPartitionResource = (deps) => {
@@ -1496,8 +1514,7 @@ var buildUrl2 = (accessPoint, partition, q) => {
1496
1514
  p.set("request_type", String(q.requestType ?? 1));
1497
1515
  p.set("user_type", String(q.userType ?? -1));
1498
1516
  if (q.field && q.field.length > 0) p.set("field", q.field.join(","));
1499
- if (q.count !== void 0) p.set("count", String(q.count));
1500
- if (q.start !== void 0) p.set("start", String(q.start));
1517
+ appendPaging(p, q.count, q.start);
1501
1518
  return apiUrl(accessPoint, "user", p);
1502
1519
  };
1503
1520
  var createUserResource = (deps) => {
@@ -1552,8 +1569,7 @@ var buildUrl3 = (accessPoint, partition, q) => {
1552
1569
  p.set("partition", String(partition));
1553
1570
  p.set("resource", String(RESOURCE_VALUE[q.resource]));
1554
1571
  p.set("active", String(q.active ?? -1));
1555
- if (q.count !== void 0) p.set("count", String(q.count));
1556
- if (q.start !== void 0) p.set("start", String(q.start));
1572
+ appendPaging(p, q.count, q.start);
1557
1573
  return apiUrl(accessPoint, "field", p);
1558
1574
  };
1559
1575
  var createFieldResource = (deps) => {
@@ -1589,7 +1605,7 @@ var buildUrl4 = (accessPoint, partition, q) => {
1589
1605
  if (q.alias !== void 0) p.set("alias", q.alias);
1590
1606
  if (q.level !== void 0) p.set("level", String(q.level));
1591
1607
  if (q.enabled !== void 0) p.set("enabled", String(q.enabled));
1592
- if (q.count !== void 0) p.set("count", String(q.count));
1608
+ appendPaging(p, q.count);
1593
1609
  return apiUrl(accessPoint, "option", p);
1594
1610
  };
1595
1611
  var withoutItems = (raw) => Object.fromEntries(Object.entries(raw).filter(([k]) => k !== "Items"));