@joymerrevent/porters-connect 0.12.0 → 0.13.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,64 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.13.0] - 2026-09-06
9
+
10
+ **User マスタが PORTERS の全 17 項目を返すようになった版**です。破壊的変更はありませんが、
11
+ `field` を省略したときの既定の挙動が変わります(下記 Changed)。
12
+
13
+ ### Added
14
+
15
+ - **User マスタが reference の全 17 項目を返すようになりました**([ADR-0060][adr60] D2)。
16
+ これまでカタログは 4 項目(`P_Id` / `P_Type` / `P_Name` / `P_Mail`)だけで、部署・電話・利用開始日・
17
+ 登録者といった項目は**型にも存在せず、読む手段がありませんでした**。追加された 13 項目はこちらです。
18
+
19
+ `P_Department`(`System[Department]` — 入れ子で `P_Id` / `P_Name`)/ `P_Telephone` / `P_Mobile` /
20
+ `P_MobileMail` / `P_UserName` / `P_TimeZone` / `P_Language` / `P_StartDate` / `P_EndDate` /
21
+ `P_RegistrationDate` / `P_RegisteredBy` / `P_UpdateDate` / `P_UpdatedBy`
22
+
23
+ ```ts
24
+ const page = await t.user.search();
25
+ page.items[0]?.P_Department; // { P_Id: number | null; P_Name: string | null } | null
26
+ page.items[0]?.P_StartDate; // ISO 8601("2026-04-01")
27
+ ```
28
+
29
+ ### Changed
30
+
31
+ - **`field` を省略した User Read が、カタログの全項目を要求するようになりました**(他のリソースと
32
+ 同じ既定の扱い)。PORTERS は `field` 省略時に 4 項目しか返さないため、そのままでは
33
+ **型が約束する 13 項目が黙って `null` になる**からです。
34
+
35
+ 4 項目だけで十分な場合は、`field` に必要なものだけを並べてください。
36
+ `field: []` は従来どおり「PORTERS 既定の 4 項目に委ねる」意味です。
37
+
38
+ ## [0.12.1] - 2026-09-03
39
+
40
+ **フェイルセーフの修正 2 件**です。どちらも「黙って壊れる/安全側に倒しすぎている」場面を直したもので、
41
+ **公開 API・型に変更はありません**(既存コードはそのまま動きます)。
42
+
43
+ ### Fixed
44
+
45
+ - **`searchAll` が「渡したクエリの全件」を必ず返すようになりました**([RV-32][rv32])。
46
+ これまではクエリ オブジェクトへの参照を保持してページごとに読み直していたため、`for await` の反復中に
47
+ 呼び出し側がそのオブジェクトを書き換えると、**同じ 1 回の `searchAll` の途中で検索条件が変わって**いました
48
+ (例外も警告も出ないまま「全件取ったつもりが途中から別条件の全件」になる)。
49
+
50
+ クエリは**最初のページを取る直前に 1 度だけ直列化**し、以降は `count` / `start` だけを差し替えます。
51
+ 入れ子(`q.condition.P_Name.part` など)の書き換えも後続ページに影響しません。クエリ ガード
52
+ (`keywords` の長さ・`itemstate` の condition 制限など)の失敗が届くタイミングは従来どおりです。
53
+ 対象は全データ系リソースと `field` / `partition` / `user`。
54
+
55
+ - **適用されていないと分かっている失敗では、`create` / `createMany` が自動リトライされるようになりました**
56
+ ([ADR-0063][adr63] / [RV-22][rv22])。非冪等な書き込みを不確定な失敗で再送しない方針(二重登録を作らない)は
57
+ 変わりません。変えたのは「不確定」の判定で、これまでは `PortersNetworkError` かどうかだけを見ていたため、
58
+ **リクエストが一度も出ていない失敗**まで「適用されたかもしれない」扱いになっていました。
59
+
60
+ - **トークン取得に失敗した場合** — 送信前なので再送しても二重登録は起きません。
61
+ - **HTTP 429** — 処理される前に拒否された応答なので、同じく対象になります
62
+ (実 PORTERS はレート超過で切断する想定なので、効くのはプロキシ経由の環境です)。
63
+
64
+ **送信後の timeout / 切断 / 5xx では、従来どおり再送しません**(適用されたかどうか分からないため)。
65
+
8
66
  ## [0.12.0] - 2026-08-31
9
67
 
10
68
  **PORTERS の全リソースに対応した版**です。データ系は 6/13 から **13/13** になり、
@@ -511,11 +569,16 @@
511
569
  [adr59]: docs/adr/0059-read-field-bare-alias.md
512
570
  [adr60]: docs/adr/0060-full-resource-coverage-direction.md
513
571
  [adr61]: docs/adr/0061-phase-resource-surface.md
572
+ [adr63]: docs/adr/0063-idempotency-guard-scope.md
573
+ [rv22]: docs/reviews/rv/0022-ratelimit-create-no-retry.md
574
+ [rv32]: docs/reviews/rv/0032-searchall-query-mutation.md
514
575
  [write-constraints]: docs/guide/write-constraints.md
515
576
  [lv]: docs/live-verification.md
516
577
  [kac]: https://keepachangelog.com/en/1.1.0/
517
578
  [semver]: https://semver.org/
518
- [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.12.0...HEAD
579
+ [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.12.1...HEAD
580
+ [0.13.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.12.1...v0.13.0
581
+ [0.12.1]: https://github.com/Joymerrevent/porters-connect/compare/v0.12.0...v0.12.1
519
582
  [0.12.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.11.0...v0.12.0
520
583
  [0.11.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.10.0...v0.11.0
521
584
  [0.10.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.9.0...v0.10.0
package/dist/index.d.ts CHANGED
@@ -2041,6 +2041,19 @@ declare const FIELDS$2: {
2041
2041
  readonly P_Type: "Number";
2042
2042
  readonly P_Name: "SinglelineText";
2043
2043
  readonly P_Mail: "Mail";
2044
+ readonly P_Department: "System[Department]";
2045
+ readonly P_Telephone: "Telephone";
2046
+ readonly P_Mobile: "Telephone";
2047
+ readonly P_MobileMail: "Mail";
2048
+ readonly P_UserName: "SinglelineText";
2049
+ readonly P_TimeZone: "SinglelineText";
2050
+ readonly P_Language: "SinglelineText";
2051
+ readonly P_StartDate: "Date";
2052
+ readonly P_EndDate: "Date";
2053
+ readonly P_RegistrationDate: "System[DateTime]";
2054
+ readonly P_RegisteredBy: "User";
2055
+ readonly P_UpdateDate: "System[DateTime]";
2056
+ readonly P_UpdatedBy: "User";
2044
2057
  };
2045
2058
  /** A decoded User (PORTERS operator). `P_Type`: 0 = standard user, 1 = system admin. */
2046
2059
  type User = ReadRecord<typeof FIELDS$2>;
@@ -2053,7 +2066,9 @@ type UserSearchQuery = {
2053
2066
  userType?: -1 | 0 | 1;
2054
2067
  /**
2055
2068
  * Output fields as **bare aliases** (`P_Name`); the library adds the `User.` prefix (ADR-0059).
2056
- * Omit to get the 4 core fields (this catalog).
2069
+ * **Omit** to fetch every catalogued field (ADR-0020); narrow it when the extra HR fields
2070
+ * (department / telephone / dates) are not interesting. Pass `[]` for PORTERS' own default —
2071
+ * the 4 core fields it returns for a fieldless read.
2057
2072
  */
2058
2073
  field?: ReadFieldAlias<typeof FIELDS$2>[];
2059
2074
  count?: number;
package/dist/index.js CHANGED
@@ -324,11 +324,13 @@ var createRequester = (o) => {
324
324
  let attempt = 0;
325
325
  for (; ; ) {
326
326
  await o.throttle.take(write);
327
+ let sent = false;
327
328
  try {
328
329
  const token = await o.auth.getAccessToken(
329
330
  forceRefresh ? { forceRefresh: true } : void 0
330
331
  );
331
332
  forceRefresh = false;
333
+ sent = true;
332
334
  const res = await o.transport.send(withAuth(req, token, write));
333
335
  return readResponse(res, parse);
334
336
  } catch (e) {
@@ -338,7 +340,9 @@ var createRequester = (o) => {
338
340
  forceRefresh = true;
339
341
  continue;
340
342
  }
341
- if (e instanceof PortersNetworkError && write && !idempotent) throw e;
343
+ const mayHaveApplied = sent && e.category !== "rateLimit";
344
+ if (mayHaveApplied && e instanceof PortersNetworkError && write && !idempotent)
345
+ throw e;
342
346
  if (e.retryable && attempt < maxRetries) {
343
347
  attempt += 1;
344
348
  await sleep2(o.backoff(attempt - 1));
@@ -915,6 +919,11 @@ var appendPaging = (p, count, start) => {
915
919
  }
916
920
  if (start !== void 0) p.set("start", String(start));
917
921
  };
922
+ var readUrlOf = (accessPoint, path, base, count, start) => {
923
+ const p = new URLSearchParams(base);
924
+ appendPaging(p, count, start);
925
+ return apiUrl(accessPoint, path, p);
926
+ };
918
927
  var paginate = async function* (fetchPage) {
919
928
  let start = 0;
920
929
  for (; ; ) {
@@ -924,6 +933,9 @@ var paginate = async function* (fetchPage) {
924
933
  if (page.items.length === 0 || start >= page.total) return;
925
934
  }
926
935
  };
936
+ var paginateOnce = async function* (prepare) {
937
+ yield* paginate(prepare());
938
+ };
927
939
 
928
940
  // src/resources/query.ts
929
941
  var KEYWORDS_MAX_CHARS = 100;
@@ -1144,7 +1156,7 @@ var firstWriteResultId = (body, path, name) => {
1144
1156
  }
1145
1157
  return first.id;
1146
1158
  };
1147
- var buildReadUrl = (accessPoint, partition, path, q, ctx) => {
1159
+ var buildReadParams = (partition, q, ctx) => {
1148
1160
  const p = new URLSearchParams();
1149
1161
  p.set("partition", String(partition));
1150
1162
  for (const [key, value] of Object.entries(ctx.params ?? {}))
@@ -1159,8 +1171,7 @@ var buildReadUrl = (accessPoint, partition, path, q, ctx) => {
1159
1171
  p.set("field", entries.join(","));
1160
1172
  }
1161
1173
  appendReadQuery(p, q, ctx);
1162
- appendPaging(p, q.count, q.start);
1163
- return apiUrl(accessPoint, path, p);
1174
+ return p;
1164
1175
  };
1165
1176
  var buildWriteUrl = (accessPoint, partition, path) => apiUrl(
1166
1177
  accessPoint,
@@ -1175,12 +1186,13 @@ var createResource = (config, deps) => {
1175
1186
  const idAlias = config.idAlias ?? "P_Id";
1176
1187
  const decode = decoderFor(config.fields);
1177
1188
  const defaultFields = Object.keys(config.fields);
1178
- const readUrl = (q) => buildReadUrl(deps.accessPoint, deps.partition, config.path, q, {
1189
+ const readParams = (q) => buildReadParams(deps.partition, q, {
1179
1190
  prefix: config.prefix,
1180
1191
  fields: fieldMap,
1181
1192
  references,
1182
1193
  params: config.readParams
1183
1194
  });
1195
+ const readUrl = (q) => readUrlOf(deps.accessPoint, config.path, readParams(q), q.count, q.start);
1184
1196
  const writeUrl = () => buildWriteUrl(deps.accessPoint, deps.partition, config.path);
1185
1197
  const firstWriteId = (body) => firstWriteResultId(body, config.path, config.name);
1186
1198
  const decoderWith = (expand) => {
@@ -1193,7 +1205,19 @@ var createResource = (config, deps) => {
1193
1205
  readUrl({ ...query, field: query.field ?? defaultFields }),
1194
1206
  decoderWith(query.expand)
1195
1207
  );
1196
- const searchAll = (query = {}) => paginate((count, start) => search({ ...query, count, start }));
1208
+ const searchAll = (query = {}) => paginateOnce(() => {
1209
+ const base = readParams({
1210
+ ...query,
1211
+ field: query.field ?? defaultFields
1212
+ });
1213
+ const decode2 = decoderWith(query.expand);
1214
+ return (count, start) => runRead(
1215
+ deps.requester,
1216
+ config.name,
1217
+ readUrlOf(deps.accessPoint, config.path, base, count, start),
1218
+ decode2
1219
+ );
1220
+ });
1197
1221
  const get = async (id, options = {}) => {
1198
1222
  const condition = { [idAlias]: { eq: id } };
1199
1223
  const page = await search({
@@ -2012,21 +2036,24 @@ var PARTITION_DESCRIPTOR = {
2012
2036
  prefix: "Partition",
2013
2037
  fields: FIELDS13
2014
2038
  };
2015
- var buildUrl = (accessPoint, q) => {
2039
+ var buildParams = (q) => {
2016
2040
  const p = new URLSearchParams();
2017
2041
  p.set("request_type", String(q.requestType ?? 1));
2018
- appendPaging(p, q.count, q.start);
2019
- return apiUrl(accessPoint, "partition", p);
2042
+ return p;
2020
2043
  };
2021
2044
  var createPartitionResource = (deps) => {
2022
2045
  const decode = decoderFor(FIELDS13);
2023
- const search = async (query = {}) => runRead(
2024
- deps.requester,
2025
- PARTITION_DESCRIPTOR.name,
2026
- buildUrl(deps.accessPoint, query),
2027
- decode
2028
- );
2029
- const searchAll = (query = {}) => paginate((count, start) => search({ ...query, count, start }));
2046
+ const readUrl = (q) => readUrlOf(deps.accessPoint, "partition", buildParams(q), q.count, q.start);
2047
+ const search = async (query = {}) => runRead(deps.requester, PARTITION_DESCRIPTOR.name, readUrl(query), decode);
2048
+ const searchAll = (query = {}) => paginateOnce(() => {
2049
+ const base = buildParams(query);
2050
+ return (count, start) => runRead(
2051
+ deps.requester,
2052
+ PARTITION_DESCRIPTOR.name,
2053
+ readUrlOf(deps.accessPoint, "partition", base, count, start),
2054
+ decode
2055
+ );
2056
+ });
2030
2057
  return { search, searchAll };
2031
2058
  };
2032
2059
 
@@ -2035,7 +2062,24 @@ var FIELDS14 = {
2035
2062
  P_Id: "System[Id]",
2036
2063
  P_Type: "Number",
2037
2064
  P_Name: "SinglelineText",
2038
- P_Mail: "Mail"
2065
+ P_Mail: "Mail",
2066
+ // 部署は User と同じ入れ子で返る(`<Department><Department.P_Id>…` — ADR-0061 案3a)。
2067
+ P_Department: "System[Department]",
2068
+ P_Telephone: "Telephone",
2069
+ P_Mobile: "Telephone",
2070
+ P_MobileMail: "Mail",
2071
+ P_UserName: "SinglelineText",
2072
+ // タイムゾーン名(`Asia/Tokyo` 等)。PORTERS は Timezone List の名前をそのまま返す。
2073
+ P_TimeZone: "SinglelineText",
2074
+ P_Language: "SinglelineText",
2075
+ P_StartDate: "Date",
2076
+ P_EndDate: "Date",
2077
+ P_RegistrationDate: "System[DateTime]",
2078
+ // 登録者・更新者は `User` 型=汎用の組み立てが `(User.P_Id,…)` を付けて要求する。
2079
+ // VERIFY(live): User Read で User 型の項目を `()` 付きで要求できるかは未確認(LV-17 と同型)。
2080
+ P_RegisteredBy: "User",
2081
+ P_UpdateDate: "System[DateTime]",
2082
+ P_UpdatedBy: "User"
2039
2083
  };
2040
2084
  var USER_DESCRIPTOR = {
2041
2085
  name: "User",
@@ -2044,29 +2088,40 @@ var USER_DESCRIPTOR = {
2044
2088
  fields: FIELDS14
2045
2089
  };
2046
2090
  var FIELD_MAP = new Map(Object.entries(FIELDS14));
2047
- var buildUrl2 = (accessPoint, partition, q) => {
2091
+ var DEFAULT_FIELDS2 = Object.keys(FIELDS14);
2092
+ var buildParams2 = (partition, q) => {
2048
2093
  const p = new URLSearchParams();
2049
2094
  p.set("partition", String(partition));
2050
2095
  p.set("request_type", String(q.requestType ?? 1));
2051
2096
  p.set("user_type", String(q.userType ?? -1));
2052
- if (q.field && q.field.length > 0) {
2097
+ const field = q.field ?? DEFAULT_FIELDS2;
2098
+ if (field.length > 0) {
2053
2099
  p.set(
2054
2100
  "field",
2055
- qualifyReadFields(USER_DESCRIPTOR.prefix, FIELD_MAP, q.field).join(",")
2101
+ qualifyReadFields(USER_DESCRIPTOR.prefix, FIELD_MAP, field).join(",")
2056
2102
  );
2057
2103
  }
2058
- appendPaging(p, q.count, q.start);
2059
- return apiUrl(accessPoint, "user", p);
2104
+ return p;
2060
2105
  };
2061
2106
  var createUserResource = (deps) => {
2062
2107
  const decode = decoderFor(FIELDS14);
2063
- const search = async (query = {}) => runRead(
2064
- deps.requester,
2065
- USER_DESCRIPTOR.name,
2066
- buildUrl2(deps.accessPoint, deps.partition, query),
2067
- decode
2108
+ const readUrl = (q) => readUrlOf(
2109
+ deps.accessPoint,
2110
+ "user",
2111
+ buildParams2(deps.partition, q),
2112
+ q.count,
2113
+ q.start
2068
2114
  );
2069
- const searchAll = (query = {}) => paginate((count, start) => search({ ...query, count, start }));
2115
+ const search = async (query = {}) => runRead(deps.requester, USER_DESCRIPTOR.name, readUrl(query), decode);
2116
+ const searchAll = (query = {}) => paginateOnce(() => {
2117
+ const base = buildParams2(deps.partition, query);
2118
+ return (count, start) => runRead(
2119
+ deps.requester,
2120
+ USER_DESCRIPTOR.name,
2121
+ readUrlOf(deps.accessPoint, "user", base, count, start),
2122
+ decode
2123
+ );
2124
+ });
2070
2125
  const current = async () => (await search({ requestType: 0, count: 1 })).items[0];
2071
2126
  return { search, searchAll, current };
2072
2127
  };
@@ -2105,23 +2160,32 @@ var FIELD_DESCRIPTOR = {
2105
2160
  prefix: "Field",
2106
2161
  fields: FIELDS15
2107
2162
  };
2108
- var buildUrl3 = (accessPoint, partition, q) => {
2163
+ var buildParams3 = (partition, q) => {
2109
2164
  const p = new URLSearchParams();
2110
2165
  p.set("partition", String(partition));
2111
2166
  p.set("resource", String(RESOURCE_VALUE[q.resource]));
2112
2167
  p.set("active", String(q.active ?? -1));
2113
- appendPaging(p, q.count, q.start);
2114
- return apiUrl(accessPoint, "field", p);
2168
+ return p;
2115
2169
  };
2116
2170
  var createFieldResource = (deps) => {
2117
2171
  const decode = decoderFor(FIELDS15);
2118
- const search = async (query) => runRead(
2119
- deps.requester,
2120
- FIELD_DESCRIPTOR.name,
2121
- buildUrl3(deps.accessPoint, deps.partition, query),
2122
- decode
2172
+ const readUrl = (q) => readUrlOf(
2173
+ deps.accessPoint,
2174
+ "field",
2175
+ buildParams3(deps.partition, q),
2176
+ q.count,
2177
+ q.start
2123
2178
  );
2124
- const searchAll = (query) => paginate((count, start) => search({ ...query, count, start }));
2179
+ const search = async (query) => runRead(deps.requester, FIELD_DESCRIPTOR.name, readUrl(query), decode);
2180
+ const searchAll = (query) => paginateOnce(() => {
2181
+ const base = buildParams3(deps.partition, query);
2182
+ return (count, start) => runRead(
2183
+ deps.requester,
2184
+ FIELD_DESCRIPTOR.name,
2185
+ readUrlOf(deps.accessPoint, "field", base, count, start),
2186
+ decode
2187
+ );
2188
+ });
2125
2189
  return { search, searchAll };
2126
2190
  };
2127
2191
 
@@ -2140,7 +2204,7 @@ var OPTION_DESCRIPTOR = {
2140
2204
  prefix: "Option",
2141
2205
  fields: FIELDS16
2142
2206
  };
2143
- var buildUrl4 = (accessPoint, partition, q) => {
2207
+ var buildUrl = (accessPoint, partition, q) => {
2144
2208
  const p = new URLSearchParams();
2145
2209
  p.set("partition", String(partition));
2146
2210
  if (q.alias !== void 0) p.set("alias", q.alias);
@@ -2164,7 +2228,7 @@ var createOptionResource = (deps) => {
2164
2228
  const search = async (query = {}) => deps.requester.request(
2165
2229
  {
2166
2230
  method: "GET",
2167
- url: buildUrl4(deps.accessPoint, deps.partition, query),
2231
+ url: buildUrl(deps.accessPoint, deps.partition, query),
2168
2232
  headers: {}
2169
2233
  },
2170
2234
  (body) => {