@joymerrevent/porters-connect 0.13.0 → 0.14.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,60 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.14.0] - 2026-09-09
9
+
10
+ **PORTERS の Data Type 17 種すべてを型で表せるようになった版**です([ADR-0060][adr60] の完了条件 D3)。
11
+ 破壊的変更はありません。追加された `Link` / `Image` は**標準項目に 1 つも存在せず**、テナントが作った
12
+ カスタム項目としてのみ現れるため、恩恵を受けるのは当該項目を持つテナントだけです。
13
+
14
+ ### Added
15
+
16
+ - **`Image`(画像)と `Link` の 2 型に対応しました**([ADR-0064][adr64])。どちらも `defineFields` の
17
+ 宣言が唯一の入口です(標準項目に存在しないため、宣言しない限り型にも読み取り結果にも現れません)。
18
+
19
+ ```ts
20
+ const fields = defineFields({
21
+ resume: (f) => ({ U_photo: f.image(), U_contact: f.link() }),
22
+ });
23
+ ```
24
+
25
+ - **`image` クエリ**で、Image 項目の `ContentType` / `Content`(Base64 本体)を読めます。
26
+ **既定は `FileName` のみ**(PORTERS 自身の既定)なので、一覧取得が画像本体で重くなりません。
27
+ 選んだサブタグだけが戻り型に出ます。
28
+
29
+ ```ts
30
+ const r = await t.resume.get(id, {
31
+ image: { U_photo: ["FileName", "Content"] },
32
+ });
33
+ r?.U_photo; // { FileName: string | null; Content: string | null }
34
+ ```
35
+
36
+ - **Image の書き込みに対応しました**。約 15000 文字のリクエスト長ガードは画像を含む書き込みでだけ外し、
37
+ かわりに **decode 後 2MB / ファイル名 255 バイト / mime 4 種(jpeg・gif・png・bmp)** を
38
+ **送信前に**検査して `PortersConfigError` で弾きます。
39
+
40
+ - **`Link` は読み取り時に 3 つの形の union** になります(Contact の ID=`number` / `UserRef` /
41
+ `DepartmentRef`)。PORTERS が種別の判別子を返さないため、**届いた XML の形**で判別します。
42
+ 書き込みは ID のみです。
43
+
44
+ ```ts
45
+ const link = r?.U_contact;
46
+ if (typeof link === "number") {
47
+ // Contact の ID。名前などは Contact API で別途取得します
48
+ } else if (link && "P_Mail" in link) {
49
+ // ユーザー型(UserRef)
50
+ }
51
+ ```
52
+
53
+ ### Changed
54
+
55
+ - **一括書き込み(`createMany` / `updateMany`)は画像を受け付けません**。一括は「1 リクエスト
56
+ 約 15000 文字」を前提に 200 件ずつへ分割しており、画像はその前提を壊すためです。何件目が画像を
57
+ 持つかを添えて**送信前に** `PortersConfigError` で落とし、単発の `create` / `update` へ誘導します。
58
+
59
+ - `Image` / `Link` は `condition` / `order` に指定できません(Image は reference が明記、
60
+ Link は記載が無いため安全側に倒しています)。指定すると**コンパイルエラー**になります。
61
+
8
62
  ## [0.13.0] - 2026-09-06
9
63
 
10
64
  **User マスタが PORTERS の全 17 項目を返すようになった版**です。破壊的変更はありませんが、
@@ -568,6 +622,7 @@
568
622
  [adr58]: docs/adr/0058-reference-expansion-read.md
569
623
  [adr59]: docs/adr/0059-read-field-bare-alias.md
570
624
  [adr60]: docs/adr/0060-full-resource-coverage-direction.md
625
+ [adr64]: docs/adr/0064-link-image-types.md
571
626
  [adr61]: docs/adr/0061-phase-resource-surface.md
572
627
  [adr63]: docs/adr/0063-idempotency-guard-scope.md
573
628
  [rv22]: docs/reviews/rv/0022-ratelimit-create-no-retry.md
@@ -576,7 +631,8 @@
576
631
  [lv]: docs/live-verification.md
577
632
  [kac]: https://keepachangelog.com/en/1.1.0/
578
633
  [semver]: https://semver.org/
579
- [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.12.1...HEAD
634
+ [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.14.0...HEAD
635
+ [0.14.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.13.0...v0.14.0
580
636
  [0.13.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.12.1...v0.13.0
581
637
  [0.12.1]: https://github.com/Joymerrevent/porters-connect/compare/v0.12.0...v0.12.1
582
638
  [0.12.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.11.0...v0.12.0
package/README.md CHANGED
@@ -219,6 +219,8 @@ await phases.create({ ResourceId: 20001, Memo: "初回接触" });
219
219
  ライブラリが既定 field を補います)。`field: []`(空配列)を渡すと API 仕様どおり**主キーのみ**を返します(件数取得など)。
220
220
  - `expand`:参照型(`System[Reference]`)の項目について、**参照先の項目も読む**(`{ P_Client: ["P_Id", "P_Name"] }`)。
221
221
  書かなければ従来どおり**参照先の ID**が返り、**書いた項目だけ**戻り型が参照レコードに変わります。1 往復で済みます。
222
+ - `image`:`Image` 型の項目について、**`ContentType` / `Content`(Base64 本体)も読む**(`{ U_photo: ["FileName", "Content"] }`)。
223
+ 書かなければ **`FileName` だけ**が返ります(PORTERS の既定)=一覧取得が画像本体で重くなりません。
222
224
  - `condition`:検索条件。`{ 項目: { 演算子: 値 } }` 形式(複数項目は AND)。演算子は Data Type ごとに
223
225
  `eq`/`gt`/`ge`/`le`/`lt`(数値・日時・Id)、`part`/`full`(テキスト)、`or`/`and`(Option・参照/ユーザー型は ID)。
224
226
  **日時の値は ISO 8601(UTC `…Z`)**で渡すと PORTERS 形式へ自動変換します。
@@ -257,6 +259,11 @@ one?.U_score; // number | null | undefined
257
259
 
258
260
  宣言しなくても読み書きはできますが(生の文字列として通ります)、**型が付かず、`field` 省略時に取得もされません**。
259
261
 
262
+ **`Image`(画像)と `Link` は、この宣言でしか扱えません**。標準項目にこの 2 型は 1 つもなく、
263
+ テナントが作った項目としてしか存在しないためです。画像は `image` クエリで本体を読み、書き込みは
264
+ **2MB / ファイル名 255 バイト / mime 4 種**を送信前に検査します(一括書き込みには混ぜられません)。
265
+ `Link` は Contact の ID・ユーザー型・部署型のいずれかを**形で判別**して返します。
266
+
260
267
  > 宣言できる型の一覧、テナントの項目を Field Read で調べる方法、複数テナントでの扱いは
261
268
  > [カスタム項目 ガイド][custom-fields-guide] にまとめています。
262
269
 
package/dist/index.d.ts CHANGED
@@ -129,7 +129,7 @@ type AuthApi = {
129
129
  getToken(): Promise<string>;
130
130
  };
131
131
 
132
- type DataType = "System[Id]" | "Number" | "DateTime" | "System[DateTime]" | "Date" | "Age" | "SinglelineText" | "MultilineText" | "Mail" | "Telephone" | "URL" | "User" | "Option" | "System[Reference]" | "System[Department]";
132
+ type DataType = "System[Id]" | "Number" | "DateTime" | "System[DateTime]" | "Date" | "Age" | "SinglelineText" | "MultilineText" | "Mail" | "Telephone" | "URL" | "User" | "Option" | "System[Reference]" | "System[Department]" | "Image" | "Link";
133
133
  /**
134
134
  * A referenced Department (`System[Department]` — ADR-0061 案3a). Read is nested exactly like
135
135
  * `User`: `<OwnerDepartment><Department><Department.P_Id>…`. Only the two fields PORTERS shows in
@@ -146,6 +146,24 @@ type UserRef = {
146
146
  P_Name: string | null;
147
147
  P_Mail: string | null;
148
148
  };
149
+ /** The sub-tags an Image field is made of (`<Alias><FileName/><ContentType/><Content/></Alias>`). */
150
+ type ImageSubField = "FileName" | "ContentType" | "Content";
151
+ /**
152
+ * A decoded Image value (ADR-0064 論点1): the sub-tags PORTERS actually returned, each empty ->
153
+ * null. Every key is **optional for the same reason a read record's fields are** — a sub-tag that
154
+ * was not requested is simply absent. A plain read asks for the bare alias, which PORTERS answers
155
+ * with `FileName` alone; `image` selects more and narrows this to exactly what it selected.
156
+ */
157
+ type ImageValue = {
158
+ [K in ImageSubField]?: string | null;
159
+ };
160
+ /**
161
+ * A decoded Link value (ADR-0064 論点4). PORTERS resolves a Link to **a Contact id, a User, or a
162
+ * Department**, decided by the tenant's own field setting, and the response carries no
163
+ * discriminator — the shapes just differ. So the value is a union and the decode reads the shape,
164
+ * which cannot disagree with what arrived. Narrow with `typeof v === "number"` / `"P_Mail" in v`.
165
+ */
166
+ type LinkValue = number | UserRef | DepartmentRef;
149
167
  /**
150
168
  * An **expanded** `System[Reference]` value: the referenced record's requested fields, decoded by
151
169
  * the referenced resource's own catalog (ADR-0058). Only a read that asked for the expansion
@@ -154,11 +172,79 @@ type UserRef = {
154
172
  type ReferenceRecord = {
155
173
  [alias: string]: FieldValue;
156
174
  };
157
- type FieldValue = string | number | string[] | UserRef | DepartmentRef | ReferenceRecord | null;
158
- type DecodedValue<D extends DataType | null> = D extends null ? string : D extends "System[Id]" | "Number" | "System[Reference]" ? number : D extends "User" ? UserRef : D extends "System[Department]" ? DepartmentRef : D extends "Option" ? string[] : string;
175
+ type FieldValue = string | number | string[] | UserRef | DepartmentRef | ImageValue | ReferenceRecord | null;
176
+ type DecodedValueOf = {
177
+ "System[Id]": number;
178
+ Number: number;
179
+ "System[Reference]": number;
180
+ User: UserRef;
181
+ "System[Department]": DepartmentRef;
182
+ Option: string[];
183
+ Image: ImageValue;
184
+ Link: LinkValue;
185
+ DateTime: string;
186
+ "System[DateTime]": string;
187
+ Date: string;
188
+ Age: string;
189
+ SinglelineText: string;
190
+ MultilineText: string;
191
+ Mail: string;
192
+ Telephone: string;
193
+ URL: string;
194
+ };
195
+ type DecodedValue<D extends DataType | null> = D extends DataType ? DecodedValueOf[D] : string;
159
196
 
197
+ /**
198
+ * The MIME types PORTERS accepts for an Image field's `ContentType` (reference: Write API - XML
199
+ * Format). Exported so the send-time guard and the static Write input agree on one list
200
+ * (ADR-0064 論点3).
201
+ */
202
+ declare const IMAGE_CONTENT_TYPES: readonly ["image/jpeg", "image/gif", "image/png", "image/bmp"];
203
+ /** One of the four MIME types an Image field accepts. */
204
+ type ImageContentType = (typeof IMAGE_CONTENT_TYPES)[number];
205
+ /**
206
+ * An Image field's write value (ADR-0064 論点3): the three sub-elements PORTERS' Write format
207
+ * names, with `Content` Base64-encoded. All three are **required** — PORTERS' sample writes the
208
+ * full element and the library has no basis for a partial write; a value is either supplied whole
209
+ * or the field is omitted (`null` / `undefined`, like every other field).
210
+ *
211
+ * The keys are spelled exactly as they read back, so a read value feeds straight back into a write
212
+ * — **once its sub-tags are known to be present**. A read part is `string | null` (null = requested
213
+ * but empty) and there is nothing to write for a null, so that check is the caller's.
214
+ *
215
+ * Size / name-length / MIME are checked **before the request goes out** (the ~15000-char request
216
+ * guard is lifted for an image write, so this is what replaces it).
217
+ *
218
+ * VERIFY(live): there is no way to *clear* an image. Whether empty sub-elements erase the value or
219
+ * are rejected is not written down, and guessing wrong would mean thinking a value was cleared
220
+ * when it was not — docs/live-verification.md (LV-22).
221
+ */
222
+ type ImageWriteValue = {
223
+ FileName: string;
224
+ ContentType: ImageContentType;
225
+ Content: string;
226
+ };
160
227
  type WritableDataType = Exclude<DataType, "System[Id]" | "System[DateTime]" | "System[Department]">;
161
- type WriteValueOf<D extends DataType | null> = D extends null ? never : D extends "User" | "System[Reference]" | "Number" ? number : D extends "Option" ? string[] : string;
228
+ type WriteValueOfType = {
229
+ Number: number;
230
+ User: number;
231
+ "System[Reference]": number;
232
+ Link: number;
233
+ Option: string[];
234
+ Image: ImageWriteValue;
235
+ DateTime: string;
236
+ Date: string;
237
+ Age: string;
238
+ SinglelineText: string;
239
+ MultilineText: string;
240
+ Mail: string;
241
+ Telephone: string;
242
+ URL: string;
243
+ "System[Id]": string;
244
+ "System[DateTime]": string;
245
+ "System[Department]": string;
246
+ };
247
+ type WriteValueOf<D extends DataType | null> = D extends DataType ? WriteValueOfType[D] : never;
162
248
 
163
249
  type FieldCatalog = Record<string, DataType | null>;
164
250
  /**
@@ -264,6 +350,53 @@ type ExpandedReadRecord<F extends FieldCatalog, R extends ReferenceMap, E> = Omi
264
350
  [K in keyof E & keyof R]?: ExpandedValue<R[K], E[K]> | null;
265
351
  };
266
352
 
353
+ /** The aliases of `F` that are Image-typed — the only ones `image` may name. */
354
+ type ImageKeys<F extends FieldCatalog> = {
355
+ [K in keyof F]: F[K] extends "Image" ? K : never;
356
+ }[keyof F];
357
+ /**
358
+ * What `image` accepts: for each Image field, which of the three sub-tags to read. Naming a field
359
+ * that is not Image-typed is a compile error, and so is an unknown sub-tag.
360
+ *
361
+ * ```ts
362
+ * const page = await t.resume.search({ image: { U_photo: ["FileName", "Content"] } });
363
+ * page.items[0]?.U_photo; // { FileName: string | null; Content: string | null }
364
+ * ```
365
+ *
366
+ * Omitting a field (or selecting nothing) sends the bare alias, which PORTERS answers with
367
+ * `FileName` alone — so a listing never drags every image body along (ADR-0064 案1a).
368
+ */
369
+ type ImageOption<F extends FieldCatalog> = {
370
+ [K in ImageKeys<F>]?: readonly ImageSubField[];
371
+ };
372
+ /**
373
+ * The value a selected Image field reads back as: exactly the sub-tags that were selected, each
374
+ * `string | null`.
375
+ *
376
+ * `[S] extends [...]` is **non-distributive** for the same reason an expanded value's is — a
377
+ * caller who types their query as the loose `SearchQuery` passes `readonly ImageSubField[] |
378
+ * undefined`, which promises nothing, and falls through to the default {@link ImageValue}.
379
+ */
380
+ type ImageSelectedValue<S> = [S] extends [
381
+ readonly (infer A extends ImageSubField)[]
382
+ ] ? {
383
+ [K in A]: string | null;
384
+ } : ImageValue;
385
+ /**
386
+ * A read record whose selected Image fields carry exactly the sub-tags they asked for. Applied on
387
+ * top of the (possibly expanded) record, so `image` and `expand` compose without either knowing
388
+ * about the other. With nothing selected `keyof I` is `never`, so this is the record itself.
389
+ *
390
+ * Deliberately **not** written as `[keyof I] extends [never] ? Rec : …`. That short-circuit reads
391
+ * like a cheap identity, but a conditional type in a method's return position makes
392
+ * `ReturnType<typeof resource.search>` resolve to `any` — which would quietly switch off every
393
+ * type-level assertion built on it (`static-types.test.ts`) instead of failing. `Omit<Rec, never>`
394
+ * costs a little type display and keeps the assertions real.
395
+ */
396
+ type ImageReadRecord<Rec, I> = Omit<Rec, keyof I> & {
397
+ [K in keyof I & keyof Rec]?: ImageSelectedValue<I[K]> | null;
398
+ };
399
+
267
400
  /** Comparable ops for numeric Ids (System[Id]); `or` matches a set of Resource Ids (`P_Id:or=1:2`). */
268
401
  type IdCondition = {
269
402
  gt?: number;
@@ -305,12 +438,40 @@ type ReferenceCondition = {
305
438
  or?: number[];
306
439
  and?: number[];
307
440
  };
441
+ /**
442
+ * The operator object each Data Type accepts in a `condition`, as a **table rather than a
443
+ * conditional chain**: every Data Type is listed exactly once, and `never` — "cannot appear in a
444
+ * condition at all" — is written out rather than inherited from a trailing branch.
445
+ *
446
+ * That difference is the point. A chain silently sends anything unmatched to `never`, so a Data
447
+ * Type added later becomes un-conditionable **without anyone deciding that**. Here it fails to
448
+ * compile until the table says which it is, and `never` stays a decision that was made.
449
+ */
450
+ type ConditionOf = {
451
+ "System[Id]": IdCondition;
452
+ Number: NumberCondition;
453
+ DateTime: TemporalCondition;
454
+ "System[DateTime]": TemporalCondition;
455
+ Date: TemporalCondition;
456
+ Age: TemporalCondition;
457
+ SinglelineText: TextCondition;
458
+ MultilineText: TextCondition;
459
+ Mail: TextCondition;
460
+ Telephone: TextCondition;
461
+ URL: TextCondition;
462
+ Option: OptionCondition;
463
+ User: ReferenceCondition;
464
+ "System[Reference]": ReferenceCondition;
465
+ "System[Department]": never;
466
+ Image: never;
467
+ Link: never;
468
+ };
308
469
  /**
309
470
  * The condition-operator object a field of Data Type `D` accepts. A field PORTERS gives no Data
310
- * Type (`null` — ADR-0056) falls through to the closing `never`, which is exactly right: the
311
- * reference says such a field cannot appear in `condition` at all.
471
+ * Type (`null` — ADR-0056) resolves to `never`, which is exactly right: the reference says such a
472
+ * field cannot appear in `condition` at all.
312
473
  */
313
- 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;
474
+ type ConditionFor<D extends DataType | null> = D extends DataType ? ConditionOf[D] : never;
314
475
  /**
315
476
  * A typed search condition over a catalog: each field maps to the operator object its Data Type
316
477
  * allows (ADR-0038 案1a). Multiple fields are AND-joined (reference). Unknown aliases / wrong
@@ -366,6 +527,21 @@ type SearchQuery<F extends FieldCatalog = FieldCatalog, R extends ReferenceMap =
366
527
  * requested twice.
367
528
  */
368
529
  expand?: Expand<R>;
530
+ /**
531
+ * Read an Image field's `ContentType` / `Content`, not just its `FileName` (ADR-0064): map an
532
+ * Image-typed field to the sub-tags you want. Only what you select comes back, and the record
533
+ * type narrows to exactly that.
534
+ *
535
+ * ```ts
536
+ * const page = await t.resume.search({ image: { U_photo: ["FileName", "Content"] } });
537
+ * page.items[0]?.U_photo; // { FileName: string | null; Content: string | null }
538
+ * ```
539
+ *
540
+ * A field left out reads back `FileName` alone — PORTERS' own default — so listing records never
541
+ * drags every image body along with it. Like `expand`, a selected alias replaces its plain
542
+ * `field` entry, so nothing is requested twice.
543
+ */
544
+ image?: ImageOption<F>;
369
545
  /** Typed AND-conditions; each field's operators derive from its Data Type (ADR-0038). */
370
546
  condition?: Condition<F>;
371
547
  /** Sort order; orderable Data Types only (Number/Date/DateTime/Age/System). */
@@ -419,18 +595,30 @@ type CreateInput<F extends FieldCatalog, Req extends keyof F> = {
419
595
  type UpdateInput<F extends FieldCatalog> = {
420
596
  [K in WritableKeys<F>]?: WriteValueOf<F[K]> | null;
421
597
  };
598
+ /**
599
+ * "No image sub-fields selected": the identity default for the image generic, mirroring
600
+ * {@link EmptyReferences}. With no keys, `ImageReadRecord` collapses back to the record it wrapped.
601
+ */
602
+ type EmptyImages = Record<never, never>;
422
603
  type Resource<F extends FieldCatalog, Req extends keyof F, R extends ReferenceMap = EmptyReferences> = {
423
- search<const E extends Expand<R> = EmptyReferences>(query?: SearchQuery<F, R> & {
604
+ search<const E extends Expand<R> = EmptyReferences, const I extends ImageOption<F> = EmptyImages>(query?: SearchQuery<F, R> & {
424
605
  expand?: E;
425
- }): Promise<ResourcePageOf<ExpandedReadRecord<F, R, E>>>;
606
+ image?: I;
607
+ }): Promise<ResourcePageOf<ImageReadRecord<ExpandedReadRecord<F, R, E>, I>>>;
426
608
  /** Auto-paginating search: yields every matching record (200 per page). */
427
- searchAll<const E extends Expand<R> = EmptyReferences>(query?: Omit<SearchQuery<F, R>, "count" | "start"> & {
609
+ searchAll<const E extends Expand<R> = EmptyReferences, const I extends ImageOption<F> = EmptyImages>(query?: Omit<SearchQuery<F, R>, "count" | "start"> & {
428
610
  expand?: E;
429
- }): AsyncIterable<ExpandedReadRecord<F, R, E>>;
430
- /** Read one record by id. `expand` reads referenced records too (ADR-0058). */
431
- get<const E extends Expand<R> = EmptyReferences>(id: number, options?: {
611
+ image?: I;
612
+ }): AsyncIterable<ImageReadRecord<ExpandedReadRecord<F, R, E>, I>>;
613
+ /**
614
+ * Read one record by id. `expand` reads referenced records too (ADR-0058); `image` picks an
615
+ * Image field's sub-tags (ADR-0064) — `get` is where asking for a `Content` belongs, since it
616
+ * fetches one record rather than a page.
617
+ */
618
+ get<const E extends Expand<R> = EmptyReferences, const I extends ImageOption<F> = EmptyImages>(id: number, options?: {
432
619
  expand?: E;
433
- }): Promise<ExpandedReadRecord<F, R, E> | undefined>;
620
+ image?: I;
621
+ }): Promise<ImageReadRecord<ExpandedReadRecord<F, R, E>, I> | undefined>;
434
622
  /** Create one record; resolves to the newly assigned id. */
435
623
  create(input: CreateInput<F, Req>): Promise<number>;
436
624
  /** Update one record by id; resolves to that id. */
@@ -2162,7 +2350,7 @@ type OptionResource = {
2162
2350
  type FieldDef<D extends DataType> = {
2163
2351
  readonly dataType: D;
2164
2352
  };
2165
- type CustomDataType = "Number" | "SinglelineText" | "MultilineText" | "Mail" | "Telephone" | "URL" | "Date" | "DateTime" | "Age" | "Option" | "User";
2353
+ type CustomDataType = "Number" | "SinglelineText" | "MultilineText" | "Mail" | "Telephone" | "URL" | "Date" | "DateTime" | "Age" | "Option" | "User" | "Image" | "Link";
2166
2354
  /** Builder passed to each resource declaration: one method per declarable Data Type. */
2167
2355
  type FieldBuilder = {
2168
2356
  number(): FieldDef<"Number">;
@@ -2176,6 +2364,16 @@ type FieldBuilder = {
2176
2364
  age(): FieldDef<"Age">;
2177
2365
  option(): FieldDef<"Option">;
2178
2366
  user(): FieldDef<"User">;
2367
+ /**
2368
+ * An Image field (FT-18). Reads back `FileName` alone unless the query's `image` option asks
2369
+ * for `ContentType` / `Content`; writes the three sub-elements, checked before send.
2370
+ */
2371
+ image(): FieldDef<"Image">;
2372
+ /**
2373
+ * A Link field (FT-20). Reads back a Contact id, a `UserRef`, or a `DepartmentRef` — whichever
2374
+ * the tenant configured, told apart by shape; writes the referenced id.
2375
+ */
2376
+ link(): FieldDef<"Link">;
2179
2377
  };
2180
2378
  /** Data resources that accept custom fields (ADR-0023 D6). Master / Attachment are excluded. */
2181
2379
  type CustomFieldResource = "candidate" | "job" | "client" | "recruiter" | "contact" | "opportunity" | "activity" | "contract" | "sales" | "process" | "resume";
@@ -2380,4 +2578,4 @@ declare const bytesToBase64: (bytes: Uint8Array) => string;
2380
2578
  /** Decode a Base64 string back to raw bytes. */
2381
2579
  declare const base64ToBytes: (b64: string) => Uint8Array;
2382
2580
 
2383
- 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 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 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 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 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 };
2581
+ 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 FieldDecls, type FieldDef, type FieldPage, type FieldResource, type FieldSearchQuery, type FieldValue, 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 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 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 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 };