@joymerrevent/porters-connect 0.7.0 → 0.8.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,27 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.8.0] - 2026-08-14
9
+
10
+ **「0 件」と「届いていない」を区別できるようにした版**です。HTTP 200 を返す中間装置
11
+ (キャプティブポータル・SSO のログイン画面・WAF の通知ページ)の応答が、これまで**正常な空ページ**として
12
+ 通っていました。公開 API の形(型・メソッド)は不変で、**正しい PORTERS 応答に対する挙動も変わりません**。
13
+
14
+ ### Fixed
15
+
16
+ - **HTTP 200 で返る「PORTERS 以外の応答」を 0 件として扱わなくなりました**([ADR-0051][adr51])。
17
+ Read 応答は**ルート要素名(`<Candidate>` などリソース名)と `<Code>` の両方**で同定します。
18
+ どちらかを欠くボディは `PortersResourceError`(`category: "unknown"` + `httpStatus: 200` + 中間装置を疑う `hint`)で拒否します。
19
+ - 従来は**キャプティブポータル・SSO のログイン画面・WAF の通知ページ**(いずれも HTTP 200)が
20
+ `total: 0` の**正常な空ページ**として返っていました。`get(id)` は `undefined` を返すため、
21
+ **「無ければ作る」コードが重複レコードを作りにいく**状態でした(データが無いのか届いていないのかを区別できない)。
22
+ - メッセージは原因を名指しします — `resource response root is <html>, expected <Candidate>` /
23
+ `resource response has no <Code> (not a PORTERS envelope)`。**観測したルート名**が載るので切り分けできます。
24
+ - **正しい PORTERS 応答に対する挙動は変わりません**([エラーハンドリング ガイド][guide]に判定表を追加)。
25
+ - ⚠️ **`createMockTransport` でモックを手書きしている場合**は、ボディに**リソース名のルート要素と `<Code>`** が
26
+ 必要です(例 `<Candidate Total="1" Count="1" Start="0"><Code>0</Code>…</Candidate>`)。
27
+ 実際の応答と同じ形にしていれば変更は不要です。
28
+
8
29
  ## [0.7.0] - 2026-08-13
9
30
 
10
31
  エラーの見え方をまとめて是正した版です。**PORTERS の応答ではない HTTP エラー**が分類されるようになり、
@@ -217,11 +238,13 @@
217
238
  [adr48]: docs/adr/0048-access-point-host-validation.md
218
239
  [adr49]: docs/adr/0049-host-port-roundtrip.md
219
240
  [adr50]: docs/adr/0050-auth-http-status-handling.md
241
+ [adr51]: docs/adr/0051-read-envelope-identification.md
220
242
  [adr47]: docs/adr/0047-access-point-scheme.md
221
243
  [oauth-guide]: docs/guide/oauth.md
222
244
  [kac]: https://keepachangelog.com/en/1.1.0/
223
245
  [semver]: https://semver.org/
224
- [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.7.0...HEAD
246
+ [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.8.0...HEAD
247
+ [0.8.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.7.0...v0.8.0
225
248
  [0.7.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.6.2...v0.7.0
226
249
  [0.6.2]: https://github.com/Joymerrevent/porters-connect/compare/v0.6.1...v0.6.2
227
250
  [0.6.1]: https://github.com/Joymerrevent/porters-connect/compare/v0.6.0...v0.6.1
package/dist/index.js CHANGED
@@ -380,7 +380,8 @@ var toInt = (v) => {
380
380
  const s = asString(v);
381
381
  return s === void 0 ? 0 : Number(s);
382
382
  };
383
- var parseResourcePage = (xml) => {
383
+ var MIDDLEBOX_HINT = "A middlebox (proxy, captive portal, SSO login page, WAF notice) may be answering instead of PORTERS. Check PORTERS_HOST and the network path from this process to the API.";
384
+ var parseResourcePage = (xml, resource) => {
384
385
  const root = asRecord(parser.parse(xml));
385
386
  const rootKey = root ? Object.keys(root)[0] : void 0;
386
387
  const body = root && rootKey ? asRecord(root[rootKey]) : void 0;
@@ -389,11 +390,21 @@ var parseResourcePage = (xml) => {
389
390
  category: "unknown"
390
391
  });
391
392
  }
393
+ if (rootKey !== resource) {
394
+ throw new PortersResourceError(
395
+ `resource response root is <${rootKey}>, expected <${resource}>`,
396
+ { category: "unknown", hint: MIDDLEBOX_HINT, context: { resource } }
397
+ );
398
+ }
399
+ if (body.Code === void 0) {
400
+ throw new PortersResourceError(
401
+ "resource response has no <Code> (not a PORTERS envelope)",
402
+ { category: "unknown", hint: MIDDLEBOX_HINT, context: { resource } }
403
+ );
404
+ }
392
405
  const code = toInt(body.Code);
393
406
  if (code !== 0) {
394
- throw resourceError(code, `resource returned code ${code}`, {
395
- resource: rootKey
396
- });
407
+ throw resourceError(code, `resource returned code ${code}`, { resource });
397
408
  }
398
409
  return {
399
410
  total: toInt(body["@_Total"]),
@@ -826,8 +837,8 @@ var decoderFor = (fields) => {
826
837
  return out;
827
838
  };
828
839
  };
829
- var runRead = (requester, url, decode) => requester.request({ method: "GET", url, headers: {} }, (body) => {
830
- const page = parseResourcePage(body);
840
+ var runRead = (requester, resource, url, decode) => requester.request({ method: "GET", url, headers: {} }, (body) => {
841
+ const page = parseResourcePage(body, resource);
831
842
  return {
832
843
  items: page.items.map(decode),
833
844
  total: page.total,
@@ -1053,6 +1064,7 @@ var createResource = (config, deps) => {
1053
1064
  const firstWriteId = (body) => firstWriteResultId(body, config.path, config.name);
1054
1065
  const search = async (query = {}) => runRead(
1055
1066
  deps.requester,
1067
+ config.name,
1056
1068
  readUrl({ ...query, field: query.field ?? defaultFields }),
1057
1069
  decode
1058
1070
  );
@@ -1329,6 +1341,7 @@ var createResumeResource = (deps, custom) => {
1329
1341
 
1330
1342
  // src/resources/attachment.ts
1331
1343
  var MAX_CONTENT_CHARS = 14e6;
1344
+ var ATTACHMENT_RESOURCE = "Attachment";
1332
1345
  var ATTACHMENT_FIELD_NAMES = [
1333
1346
  "Id",
1334
1347
  "Resource",
@@ -1388,7 +1401,7 @@ var createAttachmentResource = (deps) => {
1388
1401
  headers: {}
1389
1402
  },
1390
1403
  (body) => {
1391
- const page = parseResourcePage(body);
1404
+ const page = parseResourcePage(body, ATTACHMENT_RESOURCE);
1392
1405
  return {
1393
1406
  items: page.items.map(decodeAttachment),
1394
1407
  total: page.total,
@@ -1412,7 +1425,7 @@ var createAttachmentResource = (deps) => {
1412
1425
  headers: {},
1413
1426
  body: `<Attachment><Item>${inner}</Item></Attachment>`
1414
1427
  },
1415
- (body) => firstWriteResultId(body, "attachment", "Attachment"),
1428
+ (body) => firstWriteResultId(body, "attachment", ATTACHMENT_RESOURCE),
1416
1429
  { write: true, idempotent, unboundedBody: true }
1417
1430
  );
1418
1431
  const create = async (input) => {
@@ -1439,6 +1452,12 @@ var FIELDS6 = {
1439
1452
  P_Name: "SinglelineText",
1440
1453
  P_CompanyId: "SinglelineText"
1441
1454
  };
1455
+ var PARTITION_DESCRIPTOR = {
1456
+ name: "Partition",
1457
+ path: "partition",
1458
+ prefix: "Partition",
1459
+ fields: FIELDS6
1460
+ };
1442
1461
  var buildUrl = (accessPoint, q) => {
1443
1462
  const p = new URLSearchParams();
1444
1463
  p.set("request_type", String(q.requestType ?? 1));
@@ -1448,7 +1467,12 @@ var buildUrl = (accessPoint, q) => {
1448
1467
  };
1449
1468
  var createPartitionResource = (deps) => {
1450
1469
  const decode = decoderFor(FIELDS6);
1451
- const search = async (query = {}) => runRead(deps.requester, buildUrl(deps.accessPoint, query), decode);
1470
+ const search = async (query = {}) => runRead(
1471
+ deps.requester,
1472
+ PARTITION_DESCRIPTOR.name,
1473
+ buildUrl(deps.accessPoint, query),
1474
+ decode
1475
+ );
1452
1476
  const searchAll = (query = {}) => paginate((count, start) => search({ ...query, count, start }));
1453
1477
  return { search, searchAll };
1454
1478
  };
@@ -1460,6 +1484,12 @@ var FIELDS7 = {
1460
1484
  P_Name: "SinglelineText",
1461
1485
  P_Mail: "Mail"
1462
1486
  };
1487
+ var USER_DESCRIPTOR = {
1488
+ name: "User",
1489
+ path: "user",
1490
+ prefix: "User",
1491
+ fields: FIELDS7
1492
+ };
1463
1493
  var buildUrl2 = (accessPoint, partition, q) => {
1464
1494
  const p = new URLSearchParams();
1465
1495
  p.set("partition", String(partition));
@@ -1474,6 +1504,7 @@ var createUserResource = (deps) => {
1474
1504
  const decode = decoderFor(FIELDS7);
1475
1505
  const search = async (query = {}) => runRead(
1476
1506
  deps.requester,
1507
+ USER_DESCRIPTOR.name,
1477
1508
  buildUrl2(deps.accessPoint, deps.partition, query),
1478
1509
  decode
1479
1510
  );
@@ -1510,6 +1541,12 @@ var FIELDS8 = {
1510
1541
  P_ReferTo: "Option",
1511
1542
  P_ResourceType: "Number"
1512
1543
  };
1544
+ var FIELD_DESCRIPTOR = {
1545
+ name: "Field",
1546
+ path: "field",
1547
+ prefix: "Field",
1548
+ fields: FIELDS8
1549
+ };
1513
1550
  var buildUrl3 = (accessPoint, partition, q) => {
1514
1551
  const p = new URLSearchParams();
1515
1552
  p.set("partition", String(partition));
@@ -1523,6 +1560,7 @@ var createFieldResource = (deps) => {
1523
1560
  const decode = decoderFor(FIELDS8);
1524
1561
  const search = async (query) => runRead(
1525
1562
  deps.requester,
1563
+ FIELD_DESCRIPTOR.name,
1526
1564
  buildUrl3(deps.accessPoint, deps.partition, query),
1527
1565
  decode
1528
1566
  );
@@ -1539,6 +1577,12 @@ var FIELDS9 = {
1539
1577
  P_Type: "Number",
1540
1578
  P_Order: "Number"
1541
1579
  };
1580
+ var OPTION_DESCRIPTOR = {
1581
+ name: "Option",
1582
+ path: "option",
1583
+ prefix: "Option",
1584
+ fields: FIELDS9
1585
+ };
1542
1586
  var buildUrl4 = (accessPoint, partition, q) => {
1543
1587
  const p = new URLSearchParams();
1544
1588
  p.set("partition", String(partition));
@@ -1568,7 +1612,7 @@ var createOptionResource = (deps) => {
1568
1612
  },
1569
1613
  (body) => {
1570
1614
  const out = [];
1571
- flatten(parseResourcePage(body).items, out);
1615
+ flatten(parseResourcePage(body, OPTION_DESCRIPTOR.name).items, out);
1572
1616
  return out;
1573
1617
  }
1574
1618
  );