@joymerrevent/porters-connect 0.22.0 → 0.23.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,50 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.23.0] - 2026-09-24
9
+
10
+ **カスタム項目を宣言で `create` の必須にできるようにした版**です。あわせて、利用者の TypeScript の下限を
11
+ **5.4** と定め、それより古い版でははっきりした型エラーになるようにしました。**TypeScript 5.4 より前を使っている
12
+ 場合は破壊的変更です**(下の Changed)。
13
+
14
+ ### Added
15
+
16
+ - **カスタム項目を宣言で `create` の必須にできるようになりました**([ADR-0089][adr89])。宣言のビルダーに
17
+ `{ required: true }` を渡すと、その項目は `create` / `createMany` の入力型で必須になります。
18
+
19
+ ```ts
20
+ const fields = defineFields({
21
+ candidate: (f) => ({
22
+ U_score: f.number({ required: true }), // create で必須
23
+ U_source: f.option(), // 任意のまま
24
+ }),
25
+ });
26
+ const t = porters.tenant(1, { fields });
27
+
28
+ await t.candidate.create({ P_Owner: 5, U_score: 80 }); // U_score を渡さないとコンパイルエラー
29
+ ```
30
+
31
+ - `required` を書かなければ、これまでと同じく任意です。`update` / `updateMany` では常に任意です。
32
+ - 型で止めるだけで、実行時には検査しません(標準項目の必須と同じ扱い)。
33
+ - `TenantScope<typeof fields>` と書いた型でも必須になります。新しい型引数は要りません。
34
+ - `generateFieldDecls` は、テナントが入力必須にしている項目(Field Read の `P_Required` が `1`)に
35
+ `{ required: true }` を付けて出します。`create` で必須にしない項目は、生成したファイルから消します。
36
+ - `readCustomCatalog` の戻り値に `required`(alias → 入力必須かどうか)が増えました。
37
+ - `verifyFields` の報告に `requiredMismatch`(宣言の `required` とテナントの入力必須の食い違い)が増えました。
38
+ 読み書きは壊れないので、`ok` は変わらず、`assertFieldsMatch` も止めません。
39
+ - 新しく公開した型: `FieldOptions` / `RequiredFor` / `DeclaredRequiredOf` / `RequiredMismatch`。
40
+
41
+ ### Changed
42
+
43
+ - **(TypeScript 5.4 より前を使っている場合は破壊的)型を読むには TypeScript 5.4 以上が要ります**
44
+ ([ADR-0090][adr90])。同梱の型定義が、5.4 で入った型(`NoInfer`)を使うためです。5.4 より前の TypeScript では、
45
+ このパッケージから `import` したものを使った行が、`requires TypeScript 5.4 or later` を含む型エラーになります。
46
+ そのときは TypeScript を上げてください。`bundler` / `node16` / `node` のどの解決方式でも同じです。
47
+ - **`verifyFields` の報告を自分で組み立てているコード**(テストのスタブなど)は、`requiredMismatch: []` を
48
+ 足してください。`FieldVerification` に項目が増えたためで、`verifyFields` の戻り値を読むだけのコードは変わりません。
49
+ - 内部の検査を増やしました(利用者への影響はありません)。CI で、TypeScript の下限の版(5.4 の系で最も古い安定版)と
50
+ 開発で使う版で利用者のコードをコンパイルし、下限より前の版では上の型エラーになることを確かめます。
51
+
8
52
  ## [0.22.0] - 2026-09-23
9
53
 
10
54
  **読み取りの値の検証を 1 つ増やし、使い方ドキュメントを組み直した版**です。破壊的変更はありませんが、
@@ -1333,7 +1377,8 @@ Attachment)あるのに、受け口の形が 3 つとも違っていました
1333
1377
  [ref]: docs/usage/reference/README.md
1334
1378
  [kac]: https://keepachangelog.com/en/1.1.0/
1335
1379
  [semver]: https://semver.org/
1336
- [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.22.0...HEAD
1380
+ [unreleased]: https://github.com/Joymerrevent/porters-connect/compare/v0.23.0...HEAD
1381
+ [0.23.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.22.0...v0.23.0
1337
1382
  [0.22.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.21.0...v0.22.0
1338
1383
  [0.21.0]: https://github.com/Joymerrevent/porters-connect/compare/v0.20.1...v0.21.0
1339
1384
  [0.20.1]: https://github.com/Joymerrevent/porters-connect/compare/v0.20.0...v0.20.1
@@ -1374,3 +1419,5 @@ Attachment)あるのに、受け口の形が 3 つとも違っていました
1374
1419
  [adr87]: docs/adr/0087-tenant-scoped-field-declarations.md
1375
1420
  [howto-custom-fields]: docs/usage/topics/custom-fields.md
1376
1421
  [ref-department]: docs/usage/reference/resource-api/resources/department.md
1422
+ [adr89]: docs/adr/0089-custom-field-required-on-create.md
1423
+ [adr90]: docs/adr/0090-typescript-floor.md
package/README.md CHANGED
@@ -34,7 +34,7 @@ XML レスポンスを型付きオブジェクトに変換し、独自仕様の
34
34
  4. **付与するスコープ**の決定(リソース別に `_r` / `_w`。Read でも複数要ることがあります)。
35
35
 
36
36
  揃えかたは[始める前に][s-prereq]に、権限付与の手順は[認証を通して、疎通を確認する][s-auth]に
37
- あります。実行環境は **Node.js 22.12 以上**で、型定義は同梱です。ビルド済みの JavaScript ファイルは ESM(`import`)の 1 つですが、
37
+ あります。実行環境は **Node.js 22.12 以上**で、型定義は同梱です(型を読むには **TypeScript 5.4 以上**が要ります)。ビルド済みの JavaScript ファイルは ESM(`import`)の 1 つですが、
38
38
  CJS(`require`)からも `require("@joymerrevent/porters-connect")` で読めます([CJS から使う][s-cjs])。
39
39
 
40
40
  契約や権限付与を**待っている間**も、PORTERS に繋がずにコードとテストは書けます
package/dist/index.d.cts CHANGED
@@ -776,7 +776,7 @@ type CandidateCreateInput = CreateInput<typeof FIELDS$g, (typeof REQUIRED_ON_CRE
776
776
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
777
777
  type CandidateUpdateInput = UpdateInput<typeof FIELDS$g>;
778
778
  /** The Candidate accessor; `C` is the declared custom-field catalog merged on. */
779
- type CandidateResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$g & C, (typeof REQUIRED_ON_CREATE$b)[number]>;
779
+ type CandidateResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$g & C, (typeof REQUIRED_ON_CREATE$b)[number] | CR>;
780
780
 
781
781
  declare const FIELDS$f: {
782
782
  readonly P_Id: "System[Id]";
@@ -914,7 +914,7 @@ type JobCreateInput = CreateInput<typeof FIELDS$f, (typeof REQUIRED_ON_CREATE$a)
914
914
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
915
915
  type JobUpdateInput = UpdateInput<typeof FIELDS$f>;
916
916
  /** The Job accessor; `C` is the declared custom-field catalog merged on. */
917
- type JobResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$f & C, (typeof REQUIRED_ON_CREATE$a)[number], typeof REFERENCES$7>;
917
+ type JobResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$f & C, (typeof REQUIRED_ON_CREATE$a)[number] | CR, typeof REFERENCES$7>;
918
918
 
919
919
  declare const FIELDS$e: {
920
920
  readonly P_Id: "System[Id]";
@@ -947,7 +947,7 @@ type ClientCreateInput = CreateInput<typeof FIELDS$e, (typeof REQUIRED_ON_CREATE
947
947
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
948
948
  type ClientUpdateInput = UpdateInput<typeof FIELDS$e>;
949
949
  /** The Client accessor; `C` is the declared custom-field catalog merged on. */
950
- type ClientResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$e & C, (typeof REQUIRED_ON_CREATE$9)[number]>;
950
+ type ClientResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$e & C, (typeof REQUIRED_ON_CREATE$9)[number] | CR>;
951
951
 
952
952
  declare const FIELDS$d: {
953
953
  readonly P_Id: "System[Id]";
@@ -1015,7 +1015,7 @@ type RecruiterCreateInput = CreateInput<typeof FIELDS$d, (typeof REQUIRED_ON_CRE
1015
1015
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
1016
1016
  type RecruiterUpdateInput = UpdateInput<typeof FIELDS$d>;
1017
1017
  /** The Recruiter accessor; `C` is the declared custom-field catalog merged on. */
1018
- type RecruiterResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$d & C, (typeof REQUIRED_ON_CREATE$8)[number], typeof REFERENCES$6>;
1018
+ type RecruiterResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$d & C, (typeof REQUIRED_ON_CREATE$8)[number] | CR, typeof REFERENCES$6>;
1019
1019
 
1020
1020
  declare const FIELDS$c: {
1021
1021
  readonly P_Id: "System[Id]";
@@ -1083,7 +1083,7 @@ type ContactCreateInput = CreateInput<typeof FIELDS$c, (typeof REQUIRED_ON_CREAT
1083
1083
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
1084
1084
  type ContactUpdateInput = UpdateInput<typeof FIELDS$c>;
1085
1085
  /** The Contact accessor; `C` is the declared custom-field catalog merged on. */
1086
- type ContactResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$c & C, (typeof REQUIRED_ON_CREATE$7)[number], typeof REFERENCES$5>;
1086
+ type ContactResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$c & C, (typeof REQUIRED_ON_CREATE$7)[number] | CR, typeof REFERENCES$5>;
1087
1087
 
1088
1088
  declare const FIELDS$b: {
1089
1089
  readonly P_Id: "System[Id]";
@@ -1198,7 +1198,7 @@ type OpportunityCreateInput = CreateInput<typeof FIELDS$b, (typeof REQUIRED_ON_C
1198
1198
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
1199
1199
  type OpportunityUpdateInput = UpdateInput<typeof FIELDS$b>;
1200
1200
  /** The Opportunity accessor; `C` is the declared custom-field catalog merged on. */
1201
- type OpportunityResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$b & C, (typeof REQUIRED_ON_CREATE$6)[number], typeof REFERENCES$4>;
1201
+ type OpportunityResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$b & C, (typeof REQUIRED_ON_CREATE$6)[number] | CR, typeof REFERENCES$4>;
1202
1202
 
1203
1203
  declare const FIELDS$a: {
1204
1204
  readonly P_Id: "System[Id]";
@@ -1230,7 +1230,7 @@ type ActivityCreateInput = CreateInput<typeof FIELDS$a, (typeof REQUIRED_ON_CREA
1230
1230
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
1231
1231
  type ActivityUpdateInput = UpdateInput<typeof FIELDS$a>;
1232
1232
  /** The Activity accessor; `C` is the declared custom-field catalog merged on. */
1233
- type ActivityResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$a & C, (typeof REQUIRED_ON_CREATE$5)[number]>;
1233
+ type ActivityResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$a & C, (typeof REQUIRED_ON_CREATE$5)[number] | CR>;
1234
1234
 
1235
1235
  declare const FIELDS$9: {
1236
1236
  readonly P_Id: "System[Id]";
@@ -1311,7 +1311,7 @@ type ContractCreateInput = CreateInput<typeof FIELDS$9, (typeof REQUIRED_ON_CREA
1311
1311
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
1312
1312
  type ContractUpdateInput = UpdateInput<typeof FIELDS$9>;
1313
1313
  /** The Contract accessor; `C` is the declared custom-field catalog merged on. */
1314
- type ContractResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$9 & C, (typeof REQUIRED_ON_CREATE$4)[number], typeof REFERENCES$3>;
1314
+ type ContractResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$9 & C, (typeof REQUIRED_ON_CREATE$4)[number] | CR, typeof REFERENCES$3>;
1315
1315
 
1316
1316
  declare const FIELDS$8: {
1317
1317
  readonly P_Id: "System[Id]";
@@ -1751,7 +1751,7 @@ type SalesCreateInput = CreateInput<typeof FIELDS$8, (typeof REQUIRED_ON_CREATE$
1751
1751
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
1752
1752
  type SalesUpdateInput = UpdateInput<typeof FIELDS$8>;
1753
1753
  /** The Sales accessor; `C` is the declared custom-field catalog merged on. */
1754
- type SalesResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$8 & C, (typeof REQUIRED_ON_CREATE$3)[number], typeof REFERENCES$2>;
1754
+ type SalesResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$8 & C, (typeof REQUIRED_ON_CREATE$3)[number] | CR, typeof REFERENCES$2>;
1755
1755
 
1756
1756
  /** Resource name -> the numeric id PORTERS uses for it (docs/usage/reference/resource-api/resources-list.md). */
1757
1757
  declare const RESOURCE_VALUES: {
@@ -2212,7 +2212,7 @@ type ProcessCreateInput = CreateInput<typeof FIELDS$6, (typeof REQUIRED_ON_CREAT
2212
2212
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
2213
2213
  type ProcessUpdateInput = UpdateInput<typeof FIELDS$6>;
2214
2214
  /** The Process accessor; `C` is the declared custom-field catalog merged on. */
2215
- type ProcessResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$6 & C, (typeof REQUIRED_ON_CREATE$1)[number], typeof REFERENCES$1>;
2215
+ type ProcessResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$6 & C, (typeof REQUIRED_ON_CREATE$1)[number] | CR, typeof REFERENCES$1>;
2216
2216
 
2217
2217
  declare const FIELDS$5: {
2218
2218
  readonly P_Id: "System[Id]";
@@ -2290,7 +2290,7 @@ type ResumeCreateInput = CreateInput<typeof FIELDS$5, (typeof REQUIRED_ON_CREATE
2290
2290
  /** Fields for `update`: all optional (`null` omits, `""` clears a text field). */
2291
2291
  type ResumeUpdateInput = UpdateInput<typeof FIELDS$5>;
2292
2292
  /** The Resume accessor; `C` is the declared custom-field catalog merged on. */
2293
- type ResumeResource<C extends FieldCatalog = EmptyCatalog> = Resource<typeof FIELDS$5 & C, (typeof REQUIRED_ON_CREATE)[number], typeof REFERENCES>;
2293
+ type ResumeResource<C extends FieldCatalog = EmptyCatalog, CR extends keyof C = never> = Resource<typeof FIELDS$5 & C, (typeof REQUIRED_ON_CREATE)[number] | CR, typeof REFERENCES>;
2294
2294
 
2295
2295
  /** A decoded Attachment. A field is `null` unless it was returned (see `field`). */
2296
2296
  type Attachment = {
@@ -2550,40 +2550,56 @@ type OptionResource = {
2550
2550
  search(query?: OptionSearchQuery): Promise<Option[]>;
2551
2551
  };
2552
2552
 
2553
- /** One custom field's declared Data Type — the builder's return value. */
2554
- type FieldDef<D extends DataType> = {
2553
+ /**
2554
+ * One custom field's declaration — the builder's return value: its Data Type and whether
2555
+ * `create` requires it.
2556
+ */
2557
+ type FieldDef<D extends DataType, R extends boolean = false> = {
2555
2558
  readonly dataType: D;
2559
+ /** `true` makes the field required in `create` / `createMany` input. Type-only: no runtime check. */
2560
+ readonly required: R;
2561
+ };
2562
+ /**
2563
+ * Options every builder method takes. `required: true` makes the field required in the
2564
+ * `create` / `createMany` input type; leave it out (or `false`) and the field stays optional,
2565
+ * as it always was. `update` input never requires it.
2566
+ *
2567
+ * @example
2568
+ * defineFields({ candidate: (f) => ({ U_score: f.number({ required: true }) }) });
2569
+ */
2570
+ type FieldOptions<R extends boolean = boolean> = {
2571
+ readonly required?: R;
2556
2572
  };
2557
2573
  declare const CUSTOM_DATA_TYPES: readonly ["Number", "SinglelineText", "MultilineText", "Mail", "Telephone", "URL", "Date", "DateTime", "Age", "Option", "User", "Image", "Link"];
2558
2574
  type CustomDataType = (typeof CUSTOM_DATA_TYPES)[number];
2559
2575
  /** Builder passed to each resource declaration: one method per declarable Data Type. */
2560
2576
  type FieldBuilder = {
2561
- number(): FieldDef<"Number">;
2562
- singlelineText(): FieldDef<"SinglelineText">;
2563
- multilineText(): FieldDef<"MultilineText">;
2564
- mail(): FieldDef<"Mail">;
2565
- telephone(): FieldDef<"Telephone">;
2566
- url(): FieldDef<"URL">;
2567
- date(): FieldDef<"Date">;
2568
- dateTime(): FieldDef<"DateTime">;
2569
- age(): FieldDef<"Age">;
2570
- option(): FieldDef<"Option">;
2571
- user(): FieldDef<"User">;
2577
+ number<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"Number", NoInfer<R>>;
2578
+ singlelineText<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"SinglelineText", NoInfer<R>>;
2579
+ multilineText<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"MultilineText", NoInfer<R>>;
2580
+ mail<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"Mail", NoInfer<R>>;
2581
+ telephone<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"Telephone", NoInfer<R>>;
2582
+ url<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"URL", NoInfer<R>>;
2583
+ date<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"Date", NoInfer<R>>;
2584
+ dateTime<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"DateTime", NoInfer<R>>;
2585
+ age<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"Age", NoInfer<R>>;
2586
+ option<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"Option", NoInfer<R>>;
2587
+ user<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"User", NoInfer<R>>;
2572
2588
  /**
2573
2589
  * An Image field (FT-18). Reads back `FileName` alone unless the query's `image` option asks
2574
2590
  * for `ContentType` / `Content`; writes the three sub-elements, checked before send.
2575
2591
  */
2576
- image(): FieldDef<"Image">;
2592
+ image<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"Image", NoInfer<R>>;
2577
2593
  /**
2578
2594
  * A Link field (FT-20). Reads back a Contact id, a `UserRef`, or a `DepartmentRef` — whichever
2579
2595
  * the tenant configured, told apart by shape; writes the referenced id.
2580
2596
  */
2581
- link(): FieldDef<"Link">;
2597
+ link<R extends boolean = false>(options?: FieldOptions<R>): FieldDef<"Link", NoInfer<R>>;
2582
2598
  };
2583
2599
  /** Data resources that accept custom fields. Master / Attachment are excluded. */
2584
2600
  type CustomFieldResource = "candidate" | "job" | "client" | "recruiter" | "contact" | "opportunity" | "activity" | "contract" | "sales" | "process" | "resume";
2585
2601
  /** One resource's custom field declarations: alias -> {@link FieldDef}. */
2586
- type ResourceDecl = Record<string, FieldDef<DataType>>;
2602
+ type ResourceDecl = Record<string, FieldDef<DataType, boolean>>;
2587
2603
  /** Declaration input: per (data) resource, a builder fn returning its custom fields. */
2588
2604
  type FieldDecls = {
2589
2605
  [R in CustomFieldResource]?: (f: FieldBuilder) => ResourceDecl;
@@ -2601,6 +2617,16 @@ type CatalogOf<R extends ResourceDecl> = {
2601
2617
  type DeclaredCatalogsOf<D extends FieldDecls> = {
2602
2618
  [R in keyof D]: D[R] extends (f: FieldBuilder) => infer Out ? Out extends ResourceDecl ? CatalogOf<Out> : never : never;
2603
2619
  };
2620
+ type RequiredAliasesOf<R extends ResourceDecl> = {
2621
+ [K in keyof R]: R[K]["required"] extends true ? K : never;
2622
+ }[keyof R];
2623
+ declare const requiredOnCreateBrand: unique symbol;
2624
+ /** Per declared resource, the aliases declared `required: true` — carried on the type only. */
2625
+ type DeclaredRequiredOf<D extends FieldDecls> = {
2626
+ readonly [requiredOnCreateBrand]: {
2627
+ [R in keyof D]: D[R] extends (f: FieldBuilder) => infer Out ? Out extends ResourceDecl ? RequiredAliasesOf<Out> : never : never;
2628
+ };
2629
+ };
2604
2630
  declare const definedFieldsBrand: unique symbol;
2605
2631
  /** A validated set of custom field catalogs (branded — the client does not re-validate). */
2606
2632
  type DefinedFields<C extends DeclaredCatalogs = DeclaredCatalogs> = C & {
@@ -2608,6 +2634,14 @@ type DefinedFields<C extends DeclaredCatalogs = DeclaredCatalogs> = C & {
2608
2634
  };
2609
2635
  /** The custom catalog declared for resource `K` (or `{}` if none) — types each accessor. */
2610
2636
  type CustomFor<C extends DeclaredCatalogs, K extends CustomFieldResource> = K extends keyof C ? C[K] extends CustomCatalog ? C[K] : EmptyCatalog : EmptyCatalog;
2637
+ /**
2638
+ * The aliases of resource `K` that `create` requires because the declaration said
2639
+ * `required: true` (or `never`). Read off the phantom that {@link defineFields} puts on its
2640
+ * result type, so a scope typed `TenantScope<typeof fields>` picks it up with no extra type argument.
2641
+ */
2642
+ type RequiredFor<C extends DeclaredCatalogs, K extends CustomFieldResource> = C extends {
2643
+ readonly [requiredOnCreateBrand]: infer Rq;
2644
+ } ? K extends keyof Rq ? Extract<Rq[K], keyof CustomFor<C, K>> : never : never;
2611
2645
  /**
2612
2646
  * Declare tenant-specific custom fields per data resource. This is the validation
2613
2647
  * boundary: it throws {@link PortersConfigError} synchronously for an unknown resource key or an
@@ -2621,7 +2655,7 @@ type CustomFor<C extends DeclaredCatalogs, K extends CustomFieldResource> = K ex
2621
2655
  * });
2622
2656
  * const t = porters.tenant(1, { fields: myFields });
2623
2657
  */
2624
- declare const defineFields: <D extends FieldDecls>(decls: D) => DefinedFields<DeclaredCatalogsOf<D>>;
2658
+ declare const defineFields: <D extends FieldDecls>(decls: D) => DefinedFields<DeclaredCatalogsOf<D> & DeclaredRequiredOf<D>>;
2625
2659
 
2626
2660
  /**
2627
2661
  * The slice of a `tenant(id)` scope this tooling needs. Structural on purpose: pass
@@ -2673,6 +2707,13 @@ type TenantCustomCatalog = {
2673
2707
  * (`generateFieldDecls` has it off by default). A field PORTERS returned without a name is absent.
2674
2708
  */
2675
2709
  readonly names: Readonly<Record<string, string>>;
2710
+ /**
2711
+ * Bare alias -> whether the tenant marks the field required (`Field.P_Required` is `1`), for
2712
+ * every field in {@link TenantCustomCatalog.fields}. Any other value — `0`, absent, or one
2713
+ * PORTERS does not document — reads as `false`: a wrong `true` would make generated code demand
2714
+ * a value the tenant does not, and the caller would get a compile error with no visible reason.
2715
+ */
2716
+ readonly required: Readonly<Record<string, boolean>>;
2676
2717
  };
2677
2718
  /** Options for {@link readCustomCatalog}. */
2678
2719
  type ReadCustomCatalogOptions = {
@@ -2767,6 +2808,20 @@ type UnverifiableResource = {
2767
2808
  /** Whatever Field Read rejected with (a `PortersError`, typically permission or network). */
2768
2809
  readonly cause: unknown;
2769
2810
  };
2811
+ /**
2812
+ * Declared `required: true` while the tenant does not mark the field required, or the reverse.
2813
+ * Harmless either way — reads and writes work — so it does not clear {@link FieldVerification.ok}.
2814
+ * `declared: true, tenant: false` is a declaration stricter than the tenant (perhaps on purpose);
2815
+ * `declared: false, tenant: true` means `create` will not stop a missing value at compile time.
2816
+ */
2817
+ type RequiredMismatch = {
2818
+ readonly resource: CustomFieldResource;
2819
+ readonly alias: string;
2820
+ /** Whether the declaration says `required: true`. */
2821
+ readonly declared: boolean;
2822
+ /** Whether the tenant marks the field required (`Field.P_Required` is `1`). */
2823
+ readonly tenant: boolean;
2824
+ };
2770
2825
  /** A tenant field that exists but no declaration can express (carried through from the catalog). */
2771
2826
  type UndeclarableTenantField = UndeclarableField & {
2772
2827
  readonly resource: CustomFieldResource;
@@ -2778,8 +2833,8 @@ type FieldVerification = {
2778
2833
  * {@link FieldVerification.missing}, {@link FieldVerification.typeMismatch} or
2779
2834
  * {@link FieldVerification.unverifiable}.
2780
2835
  *
2781
- * `undeclared` and `undeclarable` do **not** clear this flag: neither breaks anything, they are
2782
- * there to be read.
2836
+ * `undeclared`, `undeclarable` and `requiredMismatch` do **not** clear this flag: none of them
2837
+ * breaks anything, they are there to be read.
2783
2838
  */
2784
2839
  readonly ok: boolean;
2785
2840
  readonly missing: readonly MissingField[];
@@ -2787,6 +2842,7 @@ type FieldVerification = {
2787
2842
  readonly undeclared: readonly UndeclaredField[];
2788
2843
  readonly unverifiable: readonly UnverifiableResource[];
2789
2844
  readonly undeclarable: readonly UndeclarableTenantField[];
2845
+ readonly requiredMismatch: readonly RequiredMismatch[];
2790
2846
  };
2791
2847
  /** Options for {@link verifyFields}. */
2792
2848
  type VerifyFieldsOptions = {
@@ -2820,7 +2876,7 @@ declare const verifyFields: (source: FieldCatalogSource, fields: DeclaredCatalog
2820
2876
  * resource that could not be read**. That last one is deliberate: "we could not check" is not
2821
2877
  * "everything is fine", and passing it silently would defeat the point of asking.
2822
2878
  *
2823
- * `undeclared` / `undeclarable` never throw — nothing is broken by either.
2879
+ * `undeclared` / `undeclarable` / `requiredMismatch` never throw — nothing is broken by any of them.
2824
2880
  *
2825
2881
  * @example
2826
2882
  * assertFieldsMatch(await verifyFields(porters.tenant(1), myFields));
@@ -2913,22 +2969,22 @@ type TenantOptions<C extends DeclaredCatalogs = EmptyCatalog> = {
2913
2969
  * (no nesting) are deliberately absent: none of them takes a partition.
2914
2970
  */
2915
2971
  type TenantScope<C extends DeclaredCatalogs = EmptyCatalog> = {
2916
- readonly candidate: CandidateResource<CustomFor<C, "candidate">>;
2917
- readonly job: JobResource<CustomFor<C, "job">>;
2918
- readonly client: ClientResource<CustomFor<C, "client">>;
2919
- readonly recruiter: RecruiterResource<CustomFor<C, "recruiter">>;
2920
- readonly contact: ContactResource<CustomFor<C, "contact">>;
2921
- readonly opportunity: OpportunityResource<CustomFor<C, "opportunity">>;
2922
- readonly activity: ActivityResource<CustomFor<C, "activity">>;
2923
- readonly contract: ContractResource<CustomFor<C, "contract">>;
2924
- readonly sales: SalesResource<CustomFor<C, "sales">>;
2972
+ readonly candidate: CandidateResource<CustomFor<C, "candidate">, RequiredFor<C, "candidate">>;
2973
+ readonly job: JobResource<CustomFor<C, "job">, RequiredFor<C, "job">>;
2974
+ readonly client: ClientResource<CustomFor<C, "client">, RequiredFor<C, "client">>;
2975
+ readonly recruiter: RecruiterResource<CustomFor<C, "recruiter">, RequiredFor<C, "recruiter">>;
2976
+ readonly contact: ContactResource<CustomFor<C, "contact">, RequiredFor<C, "contact">>;
2977
+ readonly opportunity: OpportunityResource<CustomFor<C, "opportunity">, RequiredFor<C, "opportunity">>;
2978
+ readonly activity: ActivityResource<CustomFor<C, "activity">, RequiredFor<C, "activity">>;
2979
+ readonly contract: ContractResource<CustomFor<C, "contract">, RequiredFor<C, "contract">>;
2980
+ readonly sales: SalesResource<CustomFor<C, "sales">, RequiredFor<C, "sales">>;
2925
2981
  /**
2926
2982
  * Phase history, reached through the resource it belongs to: `t.phase.of("client")`.
2927
2983
  * PORTERS requires that `resource` on every Phase call, so it is bound once.
2928
2984
  */
2929
2985
  readonly phase: PhaseAccessor;
2930
- readonly process: ProcessResource<CustomFor<C, "process">>;
2931
- readonly resume: ResumeResource<CustomFor<C, "resume">>;
2986
+ readonly process: ProcessResource<CustomFor<C, "process">, RequiredFor<C, "process">>;
2987
+ readonly resume: ResumeResource<CustomFor<C, "resume">, RequiredFor<C, "resume">>;
2932
2988
  /**
2933
2989
  * Attachments, reached through the resource they belong to: `t.attachment.of("resume")`.
2934
2990
  * PORTERS requires that `resource` on every Attachment Read, and the same value fills the
@@ -3104,4 +3160,4 @@ declare const decodeTimeOfDay: (iso: string) => string;
3104
3160
  */
3105
3161
  declare const encodeTimeOfDay: (time: string) => string;
3106
3162
 
3107
- export { type Activity, type ActivityCreateInput, type ActivityPage, type ActivityResource, type ActivitySearchQuery, type ActivityUpdateInput, type Attachment, type AttachmentAccessor, type AttachmentCreate, 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 Department, type DepartmentPage, type DepartmentRef, type DepartmentResource, type DepartmentSearchQuery, type ErrorCategory, type Expand, type ExpandedReadRecord, type FetchTransportOptions, type Field, type FieldAccessor, 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 TenantOptions, 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, decodeTimeOfDay, defineFields, encodeTimeOfDay, generateFieldDecls, rawValue, readCustomCatalog, resourceNameOf, resourceValueOf, verifyFields };
3163
+ export { type Activity, type ActivityCreateInput, type ActivityPage, type ActivityResource, type ActivitySearchQuery, type ActivityUpdateInput, type Attachment, type AttachmentAccessor, type AttachmentCreate, 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 DeclaredRequiredOf, type DefinedFields, type Department, type DepartmentPage, type DepartmentRef, type DepartmentResource, type DepartmentSearchQuery, type ErrorCategory, type Expand, type ExpandedReadRecord, type FetchTransportOptions, type Field, type FieldAccessor, type FieldBuilder, type FieldCatalogSource, type FieldDecls, type FieldDef, type FieldOptions, 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 RequiredFor, type RequiredMismatch, 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 TenantOptions, 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, decodeTimeOfDay, defineFields, encodeTimeOfDay, generateFieldDecls, rawValue, readCustomCatalog, resourceNameOf, resourceValueOf, verifyFields };