@joymerrevent/porters-connect 0.16.0 → 0.17.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,92 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.17.0] - 2026-09-16
9
+
10
+ **添付ファイルの運び方を決め、出典に無いパラメータを型から外した版**です。**破壊的変更を 2 つ**
11
+ 含みます(添付の本体は `get` でだけ取れる/Phase の Read から `keywords` / `itemstate` が消える)。
12
+
13
+ ### Added
14
+
15
+ - **`t.attachment.searchAll()`** — 200 件を超える添付を、`start` を自分で回さずに順に見られます
16
+ ([ADR-0075][adr75])。ほかの 15 エンドポイントと同じ語彙になりました。
17
+
18
+ ```ts
19
+ for await (const a of t.attachment.searchAll({
20
+ condition: { "Resource:eq": "17" },
21
+ })) {
22
+ console.log(a.fileName, a.contentType);
23
+ }
24
+ ```
25
+
26
+ 流れるのは**メタデータだけ**なので、全部を歩いてもファイル本体はダウンロードされません。
27
+
28
+ - **`createFetchTransport`** — 既定の transport を公開しました([ADR-0077][adr77])。
29
+ 1 リクエストのタイムアウト(既定 **30 秒**)を変えられます。
30
+
31
+ ```ts
32
+ import {
33
+ PortersClient,
34
+ createFetchTransport,
35
+ } from "@joymerrevent/porters-connect";
36
+
37
+ const porters = new PortersClient({
38
+ host,
39
+ appId,
40
+ appSecret,
41
+ transport: createFetchTransport({ timeoutMs: 120_000 }), // 2 分
42
+ });
43
+ ```
44
+
45
+ この 30 秒は**接続から本文の受信完了まで**で、**1 リクエストごと**に数えます(自動リトライを
46
+ 含めると最悪 `maxRetries + 1` 倍)。レートの待ち時間は含みません。`timeoutMs` は**正の整数**だけを
47
+ 受け、`0`(=即中断で「無制限」ではない)は構築時に `PortersConfigError` で弾きます。
48
+ これまで変えるには `Transport` の自前実装が必要で、`PortersNetworkError` への分類まで
49
+ 書き直すことになっていました。
50
+
51
+ - 公開した型: `AttachmentMetaField` / `AttachmentWalkQuery` / `FetchTransportOptions`。
52
+
53
+ ### Changed
54
+
55
+ - **(破壊的)添付の本体(`content`)は `get` でだけ取れます**([ADR-0075][adr75])。
56
+ `search` / `searchAll` の `field` に `"Content"` は書けません。
57
+
58
+ ```ts
59
+ await t.attachment.search({ field: ["Id", "Content"] });
60
+ // ^^^^^^^^^ 型エラーになります
61
+ const file = await t.attachment.get(900); // 本体はこちら
62
+ ```
63
+
64
+ 1 ページは最大 200 件で、PORTERS は 1 ファイル 10MB まで許します。本体を混ぜた一覧は
65
+ **読める大きさを越えることがあります** — 1 ファイル 2MB 超 × 200 件で V8 の文字列上限
66
+ (536,870,888 文字)に当たり、`RangeError` になって再送しても直りません。しかも Attachment は
67
+ **ファイルサイズを返さない**ので、「何件までなら安全か」を呼び出し側が判断することもできません。
68
+ そこで件数ではなく**メソッド**で分けました。型を外して渡した場合も、送信前に
69
+ `PortersConfigError` で止まります。
70
+
71
+ - **(破壊的)Phase の Read クエリから `keywords` / `itemstate` が消えました**([ADR-0076][adr76])。
72
+ PORTERS の `Phase - Read` はこの 2 つを Input Variables に挙げていません。Read 記事 17 本を
73
+ 数えると、共通語彙のデータ系 11 本は両方を載せ、**Phase / Attachment / マスタ 4 種は 1 本も
74
+ 載せていません**(11/11 対 0/6)ので、記事側の省略ではなく**エンドポイントごとに取るものが
75
+ 違う**と読めます。出典に無いパラメータは、無視されるのではなく **Read 全体を失敗させうる**
76
+ (Result Code 100 / 102)ため、型の側で閉じました。
77
+
78
+ ```ts
79
+ await t.phase.of("client").search({ keywords: ["山田"] });
80
+ // ^^^^^^^^ 型エラーになります
81
+ ```
82
+
83
+ **実行時は変えていません**。cast すれば今までどおり送られます(契約環境で「実は受け付ける」と
84
+ 分かったときに確かめられるように残しました)。
85
+
86
+ - **エンドポイント × 機能のマトリクスを起こしました**。PORTERS が取るものとライブラリが送るものを
87
+ 表に並べ、**reference ↔ 表 ↔ 実装を両方向で突き合わせる検査**(94 件)を足しています。
88
+ ずれているセルには必ず根拠(ADR / ライブ検証)が要る形で、今回の 2 つの破壊的変更も
89
+ この表から出てきました。パッケージの中身は変わりません。
90
+
91
+ - **既定 30 秒のタイムアウトをドキュメントに明文化しました**([上限][limits]・[失敗の扱い][failures])。
92
+ これまでどこにも書かれていませんでした。パッケージの中身は変わりません。
93
+
8
94
  ## [0.16.0] - 2026-09-15
9
95
 
10
96
  **カスタム項目を「宣言してから使う」に揃えた版**です。**破壊的変更**(`field` が未宣言の
@@ -798,11 +884,17 @@
798
884
  [adr69]: docs/adr/0069-tenant-field-catalog-tooling.md
799
885
  [adr73]: docs/adr/0073-throttle-sharing.md
800
886
  [adr74]: docs/adr/0074-custom-field-declaration-required.md
887
+ [adr75]: docs/adr/0075-attachment-search-all.md
888
+ [adr76]: docs/adr/0076-phase-read-query-surface.md
889
+ [adr77]: docs/adr/0077-fetch-transport-timeout.md
890
+ [limits]: docs/usage/concepts/limits.md
891
+ [failures]: docs/usage/howto/handle-failures.md
801
892
  [lv]: docs/live-verification.md
802
893
  [ref]: docs/usage/reference/README.md
803
894
  [kac]: https://keepachangelog.com/en/1.1.0/
804
895
  [semver]: https://semver.org/
805
896
  [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.16.0...HEAD
897
+ [0.17.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.16.0...v0.17.0
806
898
  [0.16.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.15.1...v0.16.0
807
899
  [0.15.1]: https://github.com/Joymerrevent/porters-connect/compare/v0.15.0...v0.15.1
808
900
  [0.15.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.14.0...v0.15.0
package/dist/index.d.ts CHANGED
@@ -52,6 +52,26 @@ type PartitionId = number;
52
52
  */
53
53
  type Scheme = "https" | "http";
54
54
 
55
+ type FetchTransportOptions = {
56
+ /**
57
+ * How long one request may take, in milliseconds. Default 30,000.
58
+ *
59
+ * **It covers the whole exchange** — connect, send, and reading the response body to the end —
60
+ * because the abort signal is handed to `fetch` itself. A large download therefore hits it even
61
+ * when the headers came back instantly. It is **per request**, so a retried call can take
62
+ * `maxRetries + 1` times this (plus backoff), and it does not include waiting for a throttle
63
+ * slot (throttling sits above the transport).
64
+ *
65
+ * Raise it to read large attachments over a slow link; lower it to fail fast in an interactive
66
+ * tool. Must be a positive integer — `0` would abort every request immediately, which reads
67
+ * like "no timeout" and is not (ADR-0077).
68
+ */
69
+ timeoutMs?: number;
70
+ /** Injectable fetch (tests / custom dispatcher). Default global fetch. */
71
+ fetchImpl?: typeof fetch;
72
+ };
73
+ declare const createFetchTransport: (opts?: FetchTransportOptions) => Transport;
74
+
55
75
  /** A mock reply: an XML body string (HTTP 200), or an explicit status + body. */
56
76
  type MockReply = string | {
57
77
  status?: number;
@@ -638,13 +658,38 @@ type UpdateInput<F extends FieldCatalog> = {
638
658
  * {@link EmptyReferences}. With no keys, `ImageReadRecord` collapses back to the record it wrapped.
639
659
  */
640
660
  type EmptyImages = Record<never, never>;
641
- type Resource<F extends FieldCatalog, Req extends keyof F, R extends ReferenceMap = EmptyReferences> = {
642
- search<const E extends Expand<R> = EmptyReferences, const I extends ImageOption<F> = EmptyImages>(query?: SearchQuery<F, R> & {
661
+ /**
662
+ * A query with `K` taken out — and **kept out**. `Omit` alone only stops a fresh object literal
663
+ * (excess-property checking); a variable that happens to carry the key still assigns. Re-declaring
664
+ * each removed key as `?: never` closes that hole, so `search(query)` fails whichever way the
665
+ * object was built. The runtime is unaffected: the key can still arrive through a cast (ADR-0074).
666
+ *
667
+ * Only the **endpoint-level** exclusions (`Unsupported`) get this treatment. `searchAll` keeps a
668
+ * plain `Omit` for `count` / `start`: those are not "PORTERS does not take this", they are
669
+ * "the walk decides them", and tightening that is a different decision from ADR-0076.
670
+ */
671
+ type WithoutQueryKeys<Q, K extends keyof Q> = Omit<Q, K> & {
672
+ [P in K]?: never;
673
+ };
674
+ type Resource<F extends FieldCatalog, Req extends keyof F, R extends ReferenceMap = EmptyReferences,
675
+ /**
676
+ * Query keys **this endpoint does not take** (ADR-0076). The common Read vocabulary is not
677
+ * universal: PORTERS lists `keywords` / `itemstate` for the 11 common data resources and for
678
+ * none of the others, so a resource on this factory can say which of them its own endpoint
679
+ * leaves out. `never` — the default — means "takes the whole vocabulary".
680
+ *
681
+ * Sending a parameter the endpoint does not list can fail the *whole* Read (Result Code 100 /
682
+ * 102), so the safe side is not to offer it. The runtime stays permissive (ADR-0074): a key
683
+ * forced in through a cast is still sent, which is how a live contract can test whether
684
+ * PORTERS accepts it at all.
685
+ */
686
+ Unsupported extends keyof SearchQuery<F, R> = never> = {
687
+ search<const E extends Expand<R> = EmptyReferences, const I extends ImageOption<F> = EmptyImages>(query?: WithoutQueryKeys<SearchQuery<F, R>, Unsupported> & {
643
688
  expand?: E;
644
689
  image?: I;
645
690
  }): Promise<ResourcePageOf<ImageReadRecord<ExpandedReadRecord<F, R, E>, I>>>;
646
691
  /** Auto-paginating search: yields every matching record (200 per page). */
647
- searchAll<const E extends Expand<R> = EmptyReferences, const I extends ImageOption<F> = EmptyImages>(query?: Omit<SearchQuery<F, R>, "count" | "start"> & {
692
+ searchAll<const E extends Expand<R> = EmptyReferences, const I extends ImageOption<F> = EmptyImages>(query?: Omit<WithoutQueryKeys<SearchQuery<F, R>, Unsupported>, "count" | "start"> & {
648
693
  expand?: E;
649
694
  image?: I;
650
695
  }): AsyncIterable<ImageReadRecord<ExpandedReadRecord<F, R, E>, I>>;
@@ -1734,13 +1779,23 @@ declare const REQUIRED_ON_CREATE$2: readonly ["ResourceId"];
1734
1779
  /** A decoded Phase entry: known aliases, each requested field `value | null`. */
1735
1780
  type Phase = ReadRecord<typeof FIELDS$6>;
1736
1781
  type PhasePage = ResourcePage<typeof FIELDS$6>;
1737
- type PhaseSearchQuery = SearchQuery<typeof FIELDS$6>;
1782
+ type PhaseUnsupportedQuery = "keywords" | "itemstate";
1783
+ /**
1784
+ * Phase's Read query: the common vocabulary **minus `keywords` / `itemstate`**, which
1785
+ * `Phase - Read` does not list (ADR-0076).
1786
+ */
1787
+ type PhaseSearchQuery = Omit<SearchQuery<typeof FIELDS$6>, PhaseUnsupportedQuery> & {
1788
+ [K in PhaseUnsupportedQuery]?: never;
1789
+ };
1738
1790
  /** Fields for `create`: `ResourceId` required (`Id` and `Resource` are supplied for you). */
1739
1791
  type PhaseCreateInput = CreateInput<typeof FIELDS$6, (typeof REQUIRED_ON_CREATE$2)[number]>;
1740
1792
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
1741
1793
  type PhaseUpdateInput = UpdateInput<typeof FIELDS$6>;
1742
- /** The Phase accessor for one bound resource — same shape as every other resource. */
1743
- type PhaseResource = Resource<typeof FIELDS$6, (typeof REQUIRED_ON_CREATE$2)[number]>;
1794
+ /**
1795
+ * The Phase accessor for one bound resource — the same shape as every other resource, except that
1796
+ * `search` / `searchAll` do not take `keywords` / `itemstate` (ADR-0076).
1797
+ */
1798
+ type PhaseResource = Resource<typeof FIELDS$6, (typeof REQUIRED_ON_CREATE$2)[number], EmptyReferences, PhaseUnsupportedQuery>;
1744
1799
  /**
1745
1800
  * Phase is reached through the resource whose history you want (ADR-0061 案2a):
1746
1801
  *
@@ -2188,6 +2243,11 @@ type ResumeUpdateInput = UpdateInput<typeof FIELDS$4>;
2188
2243
  /** The Resume accessor; `C` is the declared custom-field catalog merged on (ADR-0023). */
2189
2244
  type ResumeResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$4 & C, (typeof REQUIRED_ON_CREATE)[number], typeof REFERENCES>;
2190
2245
 
2246
+ /**
2247
+ * Every Attachment field **except** the body: what a listing may ask for (ADR-0075).
2248
+ * These are the aliases `search` / `searchAll` accept.
2249
+ */
2250
+ type AttachmentMetaField = "Id" | "Resource" | "ResourceId" | "ContentType" | "FileName";
2191
2251
  /** A decoded Attachment. A field is `null` unless it was returned (see `field`). */
2192
2252
  type Attachment = {
2193
2253
  id: number | null;
@@ -2208,16 +2268,19 @@ type AttachmentPage = {
2208
2268
  };
2209
2269
  type AttachmentSearchQuery = {
2210
2270
  /**
2211
- * Output fields. **Omit** to fetch metadata by default (Id / Resource / ResourceId /
2212
- * ContentType / FileName) — the large Base64 `Content` is excluded so listing doesn't download
2213
- * every file body (ADR-0020); request `["Content", …]` or use `get()` for the body. Pass `[]`
2214
- * for the API-native primary-key-only response. A non-empty list is sent verbatim.
2271
+ * Output fields — **metadata only**. Omit for all five (Id / Resource / ResourceId /
2272
+ * ContentType / FileName), or pass `[]` for the API-native primary-key-only response.
2273
+ *
2274
+ * The file body is **not** on this list: a listing never carries it, whatever the count
2275
+ * (ADR-0075). Read a body with {@link AttachmentResource.get}, one record at a time.
2215
2276
  */
2216
- field?: string[];
2277
+ field?: AttachmentMetaField[];
2217
2278
  condition?: Record<string, string>;
2218
2279
  count?: number;
2219
2280
  start?: number;
2220
2281
  };
2282
+ /** A walking Read: `count` / `start` are the walk's to decide. */
2283
+ type AttachmentWalkQuery = Omit<AttachmentSearchQuery, "count" | "start">;
2221
2284
  /** Fields for creating an Attachment. `content` is the Base64 file body. */
2222
2285
  type AttachmentCreate = {
2223
2286
  resource: number;
@@ -2234,6 +2297,16 @@ type AttachmentUpdate = {
2234
2297
  };
2235
2298
  type AttachmentResource = {
2236
2299
  search(query?: AttachmentSearchQuery): Promise<AttachmentPage>;
2300
+ /**
2301
+ * Auto-paginating search: yields every matching attachment (200 per page). Metadata only —
2302
+ * the body stays behind {@link AttachmentResource.get} (ADR-0075), so walking every attachment
2303
+ * in a partition never drags the files along with it.
2304
+ */
2305
+ searchAll(query?: AttachmentWalkQuery): AsyncIterable<Attachment>;
2306
+ /**
2307
+ * Read one attachment **with its body** (`content`). This is the only method that carries it:
2308
+ * one record at a time is a size PORTERS' own 10MB-per-file limit keeps readable (ADR-0075).
2309
+ */
2237
2310
  get(id: number): Promise<Attachment | undefined>;
2238
2311
  /** Create an Attachment; resolves to the newly assigned id. */
2239
2312
  create(input: AttachmentCreate): Promise<number>;
@@ -2827,4 +2900,4 @@ declare const bytesToBase64: (bytes: Uint8Array) => string;
2827
2900
  /** Decode a Base64 string back to raw bytes. */
2828
2901
  declare const base64ToBytes: (b64: string) => Uint8Array;
2829
2902
 
2830
- export { type Activity, type ActivityCreateInput, type ActivityPage, type ActivityResource, type ActivitySearchQuery, type ActivityUpdateInput, 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 Contact, type ContactCreateInput, type ContactPage, type ContactResource, type ContactSearchQuery, type ContactUpdateInput, type Contract, type ContractCreateInput, type ContractPage, type ContractResource, type ContractSearchQuery, type ContractUpdateInput, type CustomDataType, type CustomFieldResource, type CustomFor, type DeclaredCatalogs, type DefinedFields, type DepartmentRef, type ErrorCategory, type Expand, type ExpandedReadRecord, type Field, type FieldBuilder, type FieldCatalogSource, type FieldDecls, type FieldDef, type FieldPage, type FieldResource, type FieldSearchQuery, type FieldTypeMismatch, type FieldValue, type FieldVerification, type GenerateFieldDeclsOptions, type GetAccessTokenOptions, type ImageContentType, type ImageOption, type ImageReadRecord, type ImageSelectedValue, type ImageSubField, type ImageValue, type ImageWriteValue, type ItemState, type Job, type JobCreateInput, type JobPage, type JobResource, type JobSearchQuery, type JobUpdateInput, type LinkValue, type MissingField, type MockHandler, type MockReply, type MockTransportOptions, type Opportunity, type OpportunityCreateInput, type OpportunityPage, type OpportunityResource, type OpportunitySearchQuery, type OpportunityUpdateInput, type Option, type OptionResource, type OptionSearchQuery, type Order, type Partition, type PartitionId, type PartitionPage, type PartitionResource, type PartitionSearchQuery, type Phase, type PhaseAccessor, type PhaseCreateInput, type PhasePage, type PhaseResource, type PhaseSearchQuery, type PhaseUpdateInput, 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 ReadCustomCatalogOptions, type ReadFieldAlias, type Recruiter, type RecruiterCreateInput, type RecruiterPage, type RecruiterResource, type RecruiterSearchQuery, type RecruiterUpdateInput, type ReferenceMap, type ReferenceRecord, type ResourceName, type ResourcePageOf, type ResourceType, type Resume, type ResumeCreateInput, type ResumePage, type ResumeResource, type ResumeSearchQuery, type ResumeUpdateInput, type RevokeUrlOptions, type Sales, type SalesCreateInput, type SalesPage, type SalesResource, type SalesSearchQuery, type SalesUpdateInput, type Scheme, type Scope, type SearchQuery, type StoredTokens, type TenantCustomCatalog, type TenantScope, type Throttle, type ThrottleOptions, type TokenProvider, type TokenStore, type Transport, type TransportRequest, type TransportResponse, type UndeclarableField, type UndeclarableReason, type UndeclarableTenantField, type UndeclaredField, type UnverifiableResource, type User, type UserPage, type UserRef, type UserResource, type UserSearchQuery, type VerifyFieldsOptions, assertFieldsMatch, base64ToBytes, bytesToBase64, createMockTransport, createThrottle, defineFields, generateFieldDecls, rawValue, readCustomCatalog, verifyFields };
2903
+ export { type Activity, type ActivityCreateInput, type ActivityPage, type ActivityResource, type ActivitySearchQuery, type ActivityUpdateInput, type Attachment, type AttachmentCreate, type AttachmentMetaField, type AttachmentPage, type AttachmentResource, type AttachmentSearchQuery, type AttachmentUpdate, type AttachmentWalkQuery, 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 Contact, type ContactCreateInput, type ContactPage, type ContactResource, type ContactSearchQuery, type ContactUpdateInput, type Contract, type ContractCreateInput, type ContractPage, type ContractResource, type ContractSearchQuery, type ContractUpdateInput, type CustomDataType, type CustomFieldResource, type CustomFor, type DeclaredCatalogs, type DefinedFields, type DepartmentRef, type ErrorCategory, type Expand, type ExpandedReadRecord, type FetchTransportOptions, type Field, type FieldBuilder, type FieldCatalogSource, type FieldDecls, type FieldDef, type FieldPage, type FieldResource, type FieldSearchQuery, type FieldTypeMismatch, type FieldValue, type FieldVerification, type GenerateFieldDeclsOptions, type GetAccessTokenOptions, type ImageContentType, type ImageOption, type ImageReadRecord, type ImageSelectedValue, type ImageSubField, type ImageValue, type ImageWriteValue, type ItemState, type Job, type JobCreateInput, type JobPage, type JobResource, type JobSearchQuery, type JobUpdateInput, type LinkValue, type MissingField, type MockHandler, type MockReply, type MockTransportOptions, type Opportunity, type OpportunityCreateInput, type OpportunityPage, type OpportunityResource, type OpportunitySearchQuery, type OpportunityUpdateInput, type Option, type OptionResource, type OptionSearchQuery, type Order, type Partition, type PartitionId, type PartitionPage, type PartitionResource, type PartitionSearchQuery, type Phase, type PhaseAccessor, type PhaseCreateInput, type PhasePage, type PhaseResource, type PhaseSearchQuery, type PhaseUpdateInput, 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 ReadCustomCatalogOptions, type ReadFieldAlias, type Recruiter, type RecruiterCreateInput, type RecruiterPage, type RecruiterResource, type RecruiterSearchQuery, type RecruiterUpdateInput, type ReferenceMap, type ReferenceRecord, type ResourceName, type ResourcePageOf, type ResourceType, type Resume, type ResumeCreateInput, type ResumePage, type ResumeResource, type ResumeSearchQuery, type ResumeUpdateInput, type RevokeUrlOptions, type Sales, type SalesCreateInput, type SalesPage, type SalesResource, type SalesSearchQuery, type SalesUpdateInput, type Scheme, type Scope, type SearchQuery, type StoredTokens, type TenantCustomCatalog, type TenantScope, type Throttle, type ThrottleOptions, type TokenProvider, type TokenStore, type Transport, type TransportRequest, type TransportResponse, type UndeclarableField, type UndeclarableReason, type UndeclarableTenantField, type UndeclaredField, type UnverifiableResource, type User, type UserPage, type UserRef, type UserResource, type UserSearchQuery, type VerifyFieldsOptions, assertFieldsMatch, base64ToBytes, bytesToBase64, createFetchTransport, createMockTransport, createThrottle, defineFields, generateFieldDecls, rawValue, readCustomCatalog, verifyFields };
package/dist/index.js CHANGED
@@ -185,9 +185,19 @@ var processWarner = createInsecureSchemeWarner();
185
185
  var warnIfInsecureScheme = (scheme, host) => processWarner.warn(scheme, host);
186
186
 
187
187
  // src/http/fetch-transport.ts
188
+ var DEFAULT_TIMEOUT_MS = 3e4;
188
189
  var createFetchTransport = (opts = {}) => {
189
190
  const doFetch = opts.fetchImpl ?? fetch;
190
- const timeoutMs = opts.timeoutMs ?? 3e4;
191
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
192
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
193
+ throw new PortersConfigError(
194
+ `timeoutMs must be a positive integer, got ${JSON.stringify(opts.timeoutMs)}`,
195
+ {
196
+ category: "config",
197
+ hint: `Pass milliseconds (default ${DEFAULT_TIMEOUT_MS}). Raise it for large attachments; there is no "no timeout" value.`
198
+ }
199
+ );
200
+ }
191
201
  return {
192
202
  send: async (req) => {
193
203
  try {
@@ -2161,21 +2171,16 @@ var createPhaseAccessor = (deps) => ({
2161
2171
  // src/resources/attachment.ts
2162
2172
  var MAX_CONTENT_CHARS = 14e6;
2163
2173
  var ATTACHMENT_RESOURCE = "Attachment";
2164
- var ATTACHMENT_FIELD_NAMES = [
2165
- "Id",
2166
- "Resource",
2167
- "ResourceId",
2168
- "ContentType",
2169
- "FileName",
2170
- "Content"
2171
- ];
2172
- var DEFAULT_FIELDS = [
2174
+ var CONTENT = "Content";
2175
+ var META_FIELDS = [
2173
2176
  "Id",
2174
2177
  "Resource",
2175
2178
  "ResourceId",
2176
2179
  "ContentType",
2177
2180
  "FileName"
2178
2181
  ];
2182
+ var ATTACHMENT_FIELD_NAMES = [...META_FIELDS, CONTENT];
2183
+ var DEFAULT_FIELDS = META_FIELDS;
2179
2184
  var buildAttachmentReadUrl = (accessPoint, partition, q) => {
2180
2185
  const p = new URLSearchParams();
2181
2186
  p.set("partition", String(partition));
@@ -2200,6 +2205,16 @@ var decodeAttachment = (item) => ({
2200
2205
  content: asString(item.Content) ?? null
2201
2206
  });
2202
2207
  var tag = (name, value) => `<${name}>${encodeField("SinglelineText", String(value), name)}</${name}>`;
2208
+ var guardNoContentInListing = (field, method) => {
2209
+ if (field?.includes(CONTENT) !== true) return;
2210
+ throw new PortersConfigError(
2211
+ `${method} cannot request "${CONTENT}": a listing never carries the file body`,
2212
+ {
2213
+ category: "config",
2214
+ hint: "Read the body one record at a time with get(id); search/searchAll return metadata (fileName, contentType, \u2026)."
2215
+ }
2216
+ );
2217
+ };
2203
2218
  var guardContent = (content) => {
2204
2219
  if (content !== void 0 && content.length > MAX_CONTENT_CHARS) {
2205
2220
  throw new PortersConfigError(
@@ -2209,13 +2224,10 @@ var guardContent = (content) => {
2209
2224
  }
2210
2225
  };
2211
2226
  var createAttachmentResource = (deps) => {
2212
- const search = async (query = {}) => deps.requester.request(
2227
+ const read = (params) => deps.requester.request(
2213
2228
  {
2214
2229
  method: "GET",
2215
- url: buildAttachmentReadUrl(deps.accessPoint, deps.partition, {
2216
- ...query,
2217
- field: query.field ?? DEFAULT_FIELDS
2218
- }),
2230
+ url: buildAttachmentReadUrl(deps.accessPoint, deps.partition, params),
2219
2231
  headers: {}
2220
2232
  },
2221
2233
  (body) => {
@@ -2228,8 +2240,18 @@ var createAttachmentResource = (deps) => {
2228
2240
  };
2229
2241
  }
2230
2242
  );
2243
+ const search = async (query = {}) => {
2244
+ guardNoContentInListing(query.field, "search");
2245
+ return read({ ...query, field: query.field ?? DEFAULT_FIELDS });
2246
+ };
2247
+ const searchAll = (query = {}) => paginateOnce(() => {
2248
+ guardNoContentInListing(query.field, "searchAll");
2249
+ const field = query.field ?? DEFAULT_FIELDS;
2250
+ const condition = query.condition;
2251
+ return (count, start) => read({ field, condition, count, start });
2252
+ });
2231
2253
  const get = async (id) => {
2232
- const page = await search({
2254
+ const page = await read({
2233
2255
  field: ATTACHMENT_FIELD_NAMES,
2234
2256
  condition: { "Id:eq": String(id) },
2235
2257
  count: 1
@@ -2261,7 +2283,7 @@ var createAttachmentResource = (deps) => {
2261
2283
  if (input.content !== void 0) inner += tag("Content", input.content);
2262
2284
  return write(inner, true);
2263
2285
  };
2264
- return { search, get, create, update };
2286
+ return { search, searchAll, get, create, update };
2265
2287
  };
2266
2288
 
2267
2289
  // src/resources/partition.ts
@@ -2918,6 +2940,7 @@ export {
2918
2940
  assertFieldsMatch,
2919
2941
  base64ToBytes,
2920
2942
  bytesToBase64,
2943
+ createFetchTransport,
2921
2944
  createMockTransport,
2922
2945
  createThrottle,
2923
2946
  defineFields,