@entifix/react-integration 0.1.0-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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/context/adapters-context/adapters-context.d.ts +4 -0
  4. package/dist/context/adapters-context/adapters-context.d.ts.map +1 -0
  5. package/dist/context/adapters-context/adapters-context.js +19 -0
  6. package/dist/context/adapters-context/adapters-context.js.map +1 -0
  7. package/dist/context/adapters-context/index.d.ts +2 -0
  8. package/dist/context/adapters-context/index.d.ts.map +1 -0
  9. package/dist/context/adapters-context/index.js +3 -0
  10. package/dist/context/adapters-context/index.js.map +1 -0
  11. package/dist/hooks/use-data-loading/index.d.ts +3 -0
  12. package/dist/hooks/use-data-loading/index.d.ts.map +1 -0
  13. package/dist/hooks/use-data-loading/index.js +4 -0
  14. package/dist/hooks/use-data-loading/index.js.map +1 -0
  15. package/dist/hooks/use-data-loading/use-data-loading.d.ts +26 -0
  16. package/dist/hooks/use-data-loading/use-data-loading.d.ts.map +1 -0
  17. package/dist/hooks/use-data-loading/use-data-loading.js +167 -0
  18. package/dist/hooks/use-data-loading/use-data-loading.js.map +1 -0
  19. package/dist/hooks/use-data-loading/use-data-loading.types.d.ts +53 -0
  20. package/dist/hooks/use-data-loading/use-data-loading.types.d.ts.map +1 -0
  21. package/dist/hooks/use-data-loading/use-data-loading.types.js +3 -0
  22. package/dist/hooks/use-data-loading/use-data-loading.types.js.map +1 -0
  23. package/dist/hooks/use-entity-form/index.d.ts +4 -0
  24. package/dist/hooks/use-entity-form/index.d.ts.map +1 -0
  25. package/dist/hooks/use-entity-form/index.js +5 -0
  26. package/dist/hooks/use-entity-form/index.js.map +1 -0
  27. package/dist/hooks/use-entity-form/use-entity-form.d.ts +31 -0
  28. package/dist/hooks/use-entity-form/use-entity-form.d.ts.map +1 -0
  29. package/dist/hooks/use-entity-form/use-entity-form.helpers.d.ts +136 -0
  30. package/dist/hooks/use-entity-form/use-entity-form.helpers.d.ts.map +1 -0
  31. package/dist/hooks/use-entity-form/use-entity-form.helpers.js +337 -0
  32. package/dist/hooks/use-entity-form/use-entity-form.helpers.js.map +1 -0
  33. package/dist/hooks/use-entity-form/use-entity-form.js +209 -0
  34. package/dist/hooks/use-entity-form/use-entity-form.js.map +1 -0
  35. package/dist/hooks/use-entity-form/use-entity-form.types.d.ts +173 -0
  36. package/dist/hooks/use-entity-form/use-entity-form.types.d.ts.map +1 -0
  37. package/dist/hooks/use-entity-form/use-entity-form.types.js +3 -0
  38. package/dist/hooks/use-entity-form/use-entity-form.types.js.map +1 -0
  39. package/dist/hooks/use-entity-link-resolver/index.d.ts +2 -0
  40. package/dist/hooks/use-entity-link-resolver/index.d.ts.map +1 -0
  41. package/dist/hooks/use-entity-link-resolver/index.js +3 -0
  42. package/dist/hooks/use-entity-link-resolver/index.js.map +1 -0
  43. package/dist/hooks/use-entity-link-resolver/use-entity-link-resolver.d.ts +17 -0
  44. package/dist/hooks/use-entity-link-resolver/use-entity-link-resolver.d.ts.map +1 -0
  45. package/dist/hooks/use-entity-link-resolver/use-entity-link-resolver.js +26 -0
  46. package/dist/hooks/use-entity-link-resolver/use-entity-link-resolver.js.map +1 -0
  47. package/dist/hooks/use-entity-link-source/index.d.ts +3 -0
  48. package/dist/hooks/use-entity-link-source/index.d.ts.map +1 -0
  49. package/dist/hooks/use-entity-link-source/index.js +4 -0
  50. package/dist/hooks/use-entity-link-source/index.js.map +1 -0
  51. package/dist/hooks/use-entity-link-source/use-entity-link-source.d.ts +25 -0
  52. package/dist/hooks/use-entity-link-source/use-entity-link-source.d.ts.map +1 -0
  53. package/dist/hooks/use-entity-link-source/use-entity-link-source.js +169 -0
  54. package/dist/hooks/use-entity-link-source/use-entity-link-source.js.map +1 -0
  55. package/dist/hooks/use-entity-link-source/use-entity-link-source.types.d.ts +49 -0
  56. package/dist/hooks/use-entity-link-source/use-entity-link-source.types.d.ts.map +1 -0
  57. package/dist/hooks/use-entity-link-source/use-entity-link-source.types.js +3 -0
  58. package/dist/hooks/use-entity-link-source/use-entity-link-source.types.js.map +1 -0
  59. package/dist/hooks/use-entity-mutation/index.d.ts +3 -0
  60. package/dist/hooks/use-entity-mutation/index.d.ts.map +1 -0
  61. package/dist/hooks/use-entity-mutation/index.js +4 -0
  62. package/dist/hooks/use-entity-mutation/index.js.map +1 -0
  63. package/dist/hooks/use-entity-mutation/use-entity-mutation.d.ts +22 -0
  64. package/dist/hooks/use-entity-mutation/use-entity-mutation.d.ts.map +1 -0
  65. package/dist/hooks/use-entity-mutation/use-entity-mutation.js +88 -0
  66. package/dist/hooks/use-entity-mutation/use-entity-mutation.js.map +1 -0
  67. package/dist/hooks/use-entity-mutation/use-entity-mutation.types.d.ts +19 -0
  68. package/dist/hooks/use-entity-mutation/use-entity-mutation.types.d.ts.map +1 -0
  69. package/dist/hooks/use-entity-mutation/use-entity-mutation.types.js +3 -0
  70. package/dist/hooks/use-entity-mutation/use-entity-mutation.types.js.map +1 -0
  71. package/dist/hooks/use-entity-record/index.d.ts +3 -0
  72. package/dist/hooks/use-entity-record/index.d.ts.map +1 -0
  73. package/dist/hooks/use-entity-record/index.js +4 -0
  74. package/dist/hooks/use-entity-record/index.js.map +1 -0
  75. package/dist/hooks/use-entity-record/use-entity-record.d.ts +21 -0
  76. package/dist/hooks/use-entity-record/use-entity-record.d.ts.map +1 -0
  77. package/dist/hooks/use-entity-record/use-entity-record.js +93 -0
  78. package/dist/hooks/use-entity-record/use-entity-record.js.map +1 -0
  79. package/dist/hooks/use-entity-record/use-entity-record.types.d.ts +22 -0
  80. package/dist/hooks/use-entity-record/use-entity-record.types.d.ts.map +1 -0
  81. package/dist/hooks/use-entity-record/use-entity-record.types.js +3 -0
  82. package/dist/hooks/use-entity-record/use-entity-record.types.js.map +1 -0
  83. package/dist/hooks/use-entity-use-cases/index.d.ts +2 -0
  84. package/dist/hooks/use-entity-use-cases/index.d.ts.map +1 -0
  85. package/dist/hooks/use-entity-use-cases/index.js +3 -0
  86. package/dist/hooks/use-entity-use-cases/index.js.map +1 -0
  87. package/dist/hooks/use-entity-use-cases/use-entity-use-cases.d.ts +25 -0
  88. package/dist/hooks/use-entity-use-cases/use-entity-use-cases.d.ts.map +1 -0
  89. package/dist/hooks/use-entity-use-cases/use-entity-use-cases.js +47 -0
  90. package/dist/hooks/use-entity-use-cases/use-entity-use-cases.js.map +1 -0
  91. package/dist/hooks/use-wizard/index.d.ts +3 -0
  92. package/dist/hooks/use-wizard/index.d.ts.map +1 -0
  93. package/dist/hooks/use-wizard/index.js +4 -0
  94. package/dist/hooks/use-wizard/index.js.map +1 -0
  95. package/dist/hooks/use-wizard/use-wizard.d.ts +19 -0
  96. package/dist/hooks/use-wizard/use-wizard.d.ts.map +1 -0
  97. package/dist/hooks/use-wizard/use-wizard.js +136 -0
  98. package/dist/hooks/use-wizard/use-wizard.js.map +1 -0
  99. package/dist/hooks/use-wizard/use-wizard.types.d.ts +87 -0
  100. package/dist/hooks/use-wizard/use-wizard.types.d.ts.map +1 -0
  101. package/dist/hooks/use-wizard/use-wizard.types.js +3 -0
  102. package/dist/hooks/use-wizard/use-wizard.types.js.map +1 -0
  103. package/dist/index.d.ts +17 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +18 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/query/combine-filtering.d.ts +11 -0
  108. package/dist/query/combine-filtering.d.ts.map +1 -0
  109. package/dist/query/combine-filtering.js +18 -0
  110. package/dist/query/combine-filtering.js.map +1 -0
  111. package/dist/query/entity-query-key.d.ts +28 -0
  112. package/dist/query/entity-query-key.d.ts.map +1 -0
  113. package/dist/query/entity-query-key.js +28 -0
  114. package/dist/query/entity-query-key.js.map +1 -0
  115. package/dist/query/query-provider.d.ts +21 -0
  116. package/dist/query/query-provider.d.ts.map +1 -0
  117. package/dist/query/query-provider.js +35 -0
  118. package/dist/query/query-provider.js.map +1 -0
  119. package/dist/reactive/event-source-channel.d.ts +22 -0
  120. package/dist/reactive/event-source-channel.d.ts.map +1 -0
  121. package/dist/reactive/event-source-channel.js +93 -0
  122. package/dist/reactive/event-source-channel.js.map +1 -0
  123. package/dist/reactive/reactive-channel.d.ts +75 -0
  124. package/dist/reactive/reactive-channel.d.ts.map +1 -0
  125. package/dist/reactive/reactive-channel.js +55 -0
  126. package/dist/reactive/reactive-channel.js.map +1 -0
  127. package/dist/reactive/use-reactive-invalidation.d.ts +10 -0
  128. package/dist/reactive/use-reactive-invalidation.d.ts.map +1 -0
  129. package/dist/reactive/use-reactive-invalidation.js +25 -0
  130. package/dist/reactive/use-reactive-invalidation.js.map +1 -0
  131. package/dist/reactive/use-transaction-settlement.d.ts +41 -0
  132. package/dist/reactive/use-transaction-settlement.d.ts.map +1 -0
  133. package/dist/reactive/use-transaction-settlement.js +123 -0
  134. package/dist/reactive/use-transaction-settlement.js.map +1 -0
  135. package/package.json +73 -0
@@ -0,0 +1,173 @@
1
+ import type { Entity, EntityConstructor, EntityDraft, EntityDraftValue, EntityLinkSelection, StandardSchemaV1 } from '@entifix/core';
2
+ import type { EntityDraftMessages } from './use-entity-form.helpers.js';
3
+ /**
4
+ * Where an autosaved draft is persisted, as the form sees it.
5
+ *
6
+ * A **port**, because the store cannot be imported: `useDraft` and the zustand
7
+ * store behind it are `layer:shell`, this package is `layer:entifix`, and
8
+ * `@nx/enforce-module-boundaries` fails the build on the upward edge. Retagging
9
+ * to make it legal would put an IndexedDB store under the framework layer, so
10
+ * the shell implements this instead — `useEntityDraft` in `@entifix/next-shell`
11
+ * is the one adapter today.
12
+ *
13
+ * It lives here rather than in `@entifix/core`, and the asymmetry with
14
+ * `EntityLinkSource` is deliberate: that port sits in core because
15
+ * `@entifix/react-controls` and `@entifix/react-integration` are both
16
+ * `entifix:react` and may not import each other, so they *had* to meet below
17
+ * both. Nothing forces that here — `@entifix/next-shell` already imports this
18
+ * hook — so the port stays beside the options it joins.
19
+ *
20
+ * Everything it carries is JSON round-trippable, because a draft is written
21
+ * through `createJSONStorage`
22
+ * ([ADR 0032](../../../../../../../docs/adr/0032-what-may-live-in-an-autosaved-draft.md)).
23
+ * `EntityDraft` is that guarantee's compile-time half.
24
+ */
25
+ export interface EntityDraftStore {
26
+ /**
27
+ * The persisted draft, layered over the record's seed — how a workspace
28
+ * restores an autosaved, half-finished edit.
29
+ *
30
+ * It wins on *values*, never on *keys*: a member the entity no longer declares
31
+ * is dropped and one the draft never held keeps its seeded value, so a draft
32
+ * written under an older shape cannot leave a field uncontrolled. See
33
+ * `restoreEntityDraft`.
34
+ */
35
+ readonly draft?: EntityDraft;
36
+ /**
37
+ * Persist the draft. Called on every edit away from the seed.
38
+ *
39
+ * Must be referentially stable across renders: the hook writes from an effect
40
+ * keyed on it, so a new identity each render turns every render into a write.
41
+ */
42
+ save(draft: EntityDraft): void;
43
+ /**
44
+ * Discard the persisted draft.
45
+ *
46
+ * **The hook never calls this.** A draft is spent when the write *commits*,
47
+ * and the hook does not know whether it did — it neither fetches nor saves.
48
+ * The host that owns the mutation calls it on success. Naming it on the port
49
+ * anyway is what stops each host re-deriving the address it would clear by.
50
+ */
51
+ clear(): void;
52
+ }
53
+ export interface UseEntityFormOptions<TEntity extends Entity> {
54
+ /** Metadata source; fields and their validation rules derive from it. */
55
+ entityConstructor: EntityConstructor<TEntity>;
56
+ /**
57
+ * Copy for the four field-level validation failures. Omit and the keys
58
+ * render, which is what an adopter with no i18n library sees.
59
+ */
60
+ validationMessages?: EntityDraftMessages;
61
+ /**
62
+ * Resolves a schema issue's message, which is authored as a catalog key
63
+ * (`validation.minLength`) so a rule written in one language never reaches a
64
+ * user untranslated. `useTranslateKey` from the controls package is what a
65
+ * host passes; omit it and the message renders as authored.
66
+ */
67
+ translateKey?: (key: string, params?: Record<string, unknown>) => string;
68
+ /** The record being edited; seeds the initial draft. Omit to create. */
69
+ entity?: TEntity;
70
+ /**
71
+ * Where to persist this form's draft, so a half-finished edit survives a
72
+ * refresh. Omit and the form is ephemeral — which is what a plain route wants,
73
+ * and why autosave is opt-in rather than a flag a host can set wrong.
74
+ */
75
+ draft?: EntityDraftStore;
76
+ /**
77
+ * Edit only these members, by accessor name. Omit and the form owns them all.
78
+ *
79
+ * It narrows the **descriptors**, so it narrows seeding, coercion *and*
80
+ * validation together — which is the whole point. A wizard splits one entity
81
+ * across several steps, and without this every step would validate the whole
82
+ * record: step one could not advance until members it does not show were
83
+ * filled, and the required-ness of a field would depend on which step you
84
+ * were standing on.
85
+ *
86
+ * The submit therefore receives a **partial** draft. Merging the steps and
87
+ * rebuilding the entity is the host's job, exactly as it already is for one
88
+ * form ([ADR 0045](../../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).
89
+ *
90
+ * A fresh array literal per render is fine: the memo behind it keys on the
91
+ * names rather than the array's identity.
92
+ */
93
+ fields?: readonly string[];
94
+ /**
95
+ * A Standard Schema (Zod, Valibot, ArkType — anything exposing `~standard`)
96
+ * for the rules metadata cannot express: regex, min/max, cross-field.
97
+ *
98
+ * Authored against the **string draft**, so it coerces (`z.coerce.number()`)
99
+ * rather than expecting typed values. Each message is a catalog key
100
+ * (`validation.minLength`), resolved against the `controls` namespace with the
101
+ * literal as its fallback — a rule written in one language would otherwise
102
+ * reach the user untranslated. An issue with no path becomes `formError`.
103
+ *
104
+ * Synchronous rules only: validation runs in a synchronous pass, so an async
105
+ * schema throws rather than silently passing. Put asynchronous checks — a
106
+ * uniqueness lookup, say — behind {@link UseEntityFormOptions.validate}.
107
+ */
108
+ schema?: StandardSchemaV1;
109
+ /**
110
+ * Extra validation beyond the metadata and schema rules, returning a
111
+ * field-name → message map. Applied last, so it wins on conflict.
112
+ */
113
+ validate?: (values: EntityDraft) => Record<string, string>;
114
+ /** Called with the draft once it passes validation. */
115
+ onSubmit: (values: EntityDraft) => void | Promise<void>;
116
+ }
117
+ export interface UseEntityFormResult {
118
+ /** The current draft. Feed straight into `EntityForm`'s `values`. */
119
+ values: EntityDraft;
120
+ /** Field-name → message, surfaced only after a submit attempt. */
121
+ errors: Record<string, string>;
122
+ /**
123
+ * The message that belongs to no single field — a cross-field schema rule.
124
+ * Feed into `EntityForm`'s `formError`, which has nowhere else to show it.
125
+ */
126
+ formError?: string;
127
+ /**
128
+ * Feed into `EntityForm`'s `onFieldChange`.
129
+ *
130
+ * A scalar takes a string; a `composition` takes its list of rows, the one
131
+ * member with no lossless string form.
132
+ */
133
+ setField: (name: string, value: EntityDraftValue) => void;
134
+ /**
135
+ * The instances behind the relation ids the draft holds — seeded from the
136
+ * record's already-materialized links, extended by every pick, and refilled
137
+ * from a resolved id by {@link UseEntityFormResult.hydrateLink}.
138
+ *
139
+ * Pass to `applyEntityLinks` at submit: the draft alone cannot express an
140
+ * embedded relation, because an id is not an entity. It is not persisted — a
141
+ * restored draft arrives with ids and no instances, which is what
142
+ * `hydrateLink` exists to repair.
143
+ */
144
+ links: EntityLinkSelection;
145
+ /**
146
+ * Feed into `EntityForm`'s `onLinkChange`. Records the picked instance and
147
+ * writes its id into the draft, which stays the serializable source of truth.
148
+ */
149
+ setLink: (name: string, entity: Entity | undefined) => void;
150
+ /**
151
+ * Record an instance resolved from an id the draft already held — the restore
152
+ * path, not a pick.
153
+ *
154
+ * Unlike {@link UseEntityFormResult.setLink} it writes only the sidecar, so it
155
+ * cannot dirty a form the user has not touched. It also never overwrites an
156
+ * entry that is already there: a pick in flight is newer than a lookup that
157
+ * started before it.
158
+ */
159
+ hydrateLink: (name: string, resolved: Entity) => void;
160
+ /**
161
+ * Validate, then submit when clean. Feed into `EntityForm`'s `onSubmit`.
162
+ *
163
+ * **Resolves `true` only when the submit actually ran**, so a caller that has
164
+ * to gate on the outcome — a wizard, whose "Siguiente" *is* the step's submit
165
+ * — can await it instead of awaiting a tick and re-reading `errors`. A caller
166
+ * with nothing to gate ignores the promise and is unaffected, which is every
167
+ * existing one.
168
+ */
169
+ submit: () => Promise<boolean>;
170
+ /** True once any field has been edited away from its seed. */
171
+ isDirty: boolean;
172
+ }
173
+ //# sourceMappingURL=use-entity-form.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-entity-form.types.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-form/use-entity-form.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB,CAAC,OAAO,SAAS,MAAM;IAC1D,yEAAyE;IACzE,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC9C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,mBAAmB,CAAC;IACzC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC;IACzE,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3D,uDAAuD;IACvD,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,MAAM,EAAE,WAAW,CAAC;IACpB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC1D;;;;;;;;;OASG;IACH,KAAK,EAAE,mBAAmB,CAAC;IAC3B;;;OAGG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC5D;;;;;;;;OAQG;IACH,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD;;;;;;;;OAQG;IACH,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;CAClB"}
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=use-entity-form.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/use-entity-form/use-entity-form.types.ts"],"sourcesContent":["import type {\n Entity,\n EntityConstructor,\n EntityDraft,\n EntityDraftValue,\n EntityLinkSelection,\n StandardSchemaV1,\n} from '@entifix/core';\n\nimport type { EntityDraftMessages } from './use-entity-form.helpers.js';\n\n/**\n * Where an autosaved draft is persisted, as the form sees it.\n *\n * A **port**, because the store cannot be imported: `useDraft` and the zustand\n * store behind it are `layer:shell`, this package is `layer:entifix`, and\n * `@nx/enforce-module-boundaries` fails the build on the upward edge. Retagging\n * to make it legal would put an IndexedDB store under the framework layer, so\n * the shell implements this instead — `useEntityDraft` in `@entifix/next-shell`\n * is the one adapter today.\n *\n * It lives here rather than in `@entifix/core`, and the asymmetry with\n * `EntityLinkSource` is deliberate: that port sits in core because\n * `@entifix/react-controls` and `@entifix/react-integration` are both\n * `entifix:react` and may not import each other, so they *had* to meet below\n * both. Nothing forces that here — `@entifix/next-shell` already imports this\n * hook — so the port stays beside the options it joins.\n *\n * Everything it carries is JSON round-trippable, because a draft is written\n * through `createJSONStorage`\n * ([ADR 0032](../../../../../../../docs/adr/0032-what-may-live-in-an-autosaved-draft.md)).\n * `EntityDraft` is that guarantee's compile-time half.\n */\nexport interface EntityDraftStore {\n /**\n * The persisted draft, layered over the record's seed — how a workspace\n * restores an autosaved, half-finished edit.\n *\n * It wins on *values*, never on *keys*: a member the entity no longer declares\n * is dropped and one the draft never held keeps its seeded value, so a draft\n * written under an older shape cannot leave a field uncontrolled. See\n * `restoreEntityDraft`.\n */\n readonly draft?: EntityDraft;\n /**\n * Persist the draft. Called on every edit away from the seed.\n *\n * Must be referentially stable across renders: the hook writes from an effect\n * keyed on it, so a new identity each render turns every render into a write.\n */\n save(draft: EntityDraft): void;\n /**\n * Discard the persisted draft.\n *\n * **The hook never calls this.** A draft is spent when the write *commits*,\n * and the hook does not know whether it did — it neither fetches nor saves.\n * The host that owns the mutation calls it on success. Naming it on the port\n * anyway is what stops each host re-deriving the address it would clear by.\n */\n clear(): void;\n}\n\nexport interface UseEntityFormOptions<TEntity extends Entity> {\n /** Metadata source; fields and their validation rules derive from it. */\n entityConstructor: EntityConstructor<TEntity>;\n /**\n * Copy for the four field-level validation failures. Omit and the keys\n * render, which is what an adopter with no i18n library sees.\n */\n validationMessages?: EntityDraftMessages;\n /**\n * Resolves a schema issue's message, which is authored as a catalog key\n * (`validation.minLength`) so a rule written in one language never reaches a\n * user untranslated. `useTranslateKey` from the controls package is what a\n * host passes; omit it and the message renders as authored.\n */\n translateKey?: (key: string, params?: Record<string, unknown>) => string;\n /** The record being edited; seeds the initial draft. Omit to create. */\n entity?: TEntity;\n /**\n * Where to persist this form's draft, so a half-finished edit survives a\n * refresh. Omit and the form is ephemeral — which is what a plain route wants,\n * and why autosave is opt-in rather than a flag a host can set wrong.\n */\n draft?: EntityDraftStore;\n /**\n * Edit only these members, by accessor name. Omit and the form owns them all.\n *\n * It narrows the **descriptors**, so it narrows seeding, coercion *and*\n * validation together — which is the whole point. A wizard splits one entity\n * across several steps, and without this every step would validate the whole\n * record: step one could not advance until members it does not show were\n * filled, and the required-ness of a field would depend on which step you\n * were standing on.\n *\n * The submit therefore receives a **partial** draft. Merging the steps and\n * rebuilding the entity is the host's job, exactly as it already is for one\n * form ([ADR 0045](../../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).\n *\n * A fresh array literal per render is fine: the memo behind it keys on the\n * names rather than the array's identity.\n */\n fields?: readonly string[];\n /**\n * A Standard Schema (Zod, Valibot, ArkType — anything exposing `~standard`)\n * for the rules metadata cannot express: regex, min/max, cross-field.\n *\n * Authored against the **string draft**, so it coerces (`z.coerce.number()`)\n * rather than expecting typed values. Each message is a catalog key\n * (`validation.minLength`), resolved against the `controls` namespace with the\n * literal as its fallback — a rule written in one language would otherwise\n * reach the user untranslated. An issue with no path becomes `formError`.\n *\n * Synchronous rules only: validation runs in a synchronous pass, so an async\n * schema throws rather than silently passing. Put asynchronous checks — a\n * uniqueness lookup, say — behind {@link UseEntityFormOptions.validate}.\n */\n schema?: StandardSchemaV1;\n /**\n * Extra validation beyond the metadata and schema rules, returning a\n * field-name → message map. Applied last, so it wins on conflict.\n */\n validate?: (values: EntityDraft) => Record<string, string>;\n /** Called with the draft once it passes validation. */\n onSubmit: (values: EntityDraft) => void | Promise<void>;\n}\n\nexport interface UseEntityFormResult {\n /** The current draft. Feed straight into `EntityForm`'s `values`. */\n values: EntityDraft;\n /** Field-name → message, surfaced only after a submit attempt. */\n errors: Record<string, string>;\n /**\n * The message that belongs to no single field — a cross-field schema rule.\n * Feed into `EntityForm`'s `formError`, which has nowhere else to show it.\n */\n formError?: string;\n /**\n * Feed into `EntityForm`'s `onFieldChange`.\n *\n * A scalar takes a string; a `composition` takes its list of rows, the one\n * member with no lossless string form.\n */\n setField: (name: string, value: EntityDraftValue) => void;\n /**\n * The instances behind the relation ids the draft holds — seeded from the\n * record's already-materialized links, extended by every pick, and refilled\n * from a resolved id by {@link UseEntityFormResult.hydrateLink}.\n *\n * Pass to `applyEntityLinks` at submit: the draft alone cannot express an\n * embedded relation, because an id is not an entity. It is not persisted — a\n * restored draft arrives with ids and no instances, which is what\n * `hydrateLink` exists to repair.\n */\n links: EntityLinkSelection;\n /**\n * Feed into `EntityForm`'s `onLinkChange`. Records the picked instance and\n * writes its id into the draft, which stays the serializable source of truth.\n */\n setLink: (name: string, entity: Entity | undefined) => void;\n /**\n * Record an instance resolved from an id the draft already held — the restore\n * path, not a pick.\n *\n * Unlike {@link UseEntityFormResult.setLink} it writes only the sidecar, so it\n * cannot dirty a form the user has not touched. It also never overwrites an\n * entry that is already there: a pick in flight is newer than a lookup that\n * started before it.\n */\n hydrateLink: (name: string, resolved: Entity) => void;\n /**\n * Validate, then submit when clean. Feed into `EntityForm`'s `onSubmit`.\n *\n * **Resolves `true` only when the submit actually ran**, so a caller that has\n * to gate on the outcome — a wizard, whose \"Siguiente\" *is* the step's submit\n * — can await it instead of awaiting a tick and re-reading `errors`. A caller\n * with nothing to gate ignores the promise and is unaffected, which is every\n * existing one.\n */\n submit: () => Promise<boolean>;\n /** True once any field has been edited away from its seed. */\n isDirty: boolean;\n}\n"],"names":[],"mappings":"AA+HA,WAuDC"}
@@ -0,0 +1,2 @@
1
+ export * from './use-entity-link-resolver.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-link-resolver/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './use-entity-link-resolver.js';
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/use-entity-link-resolver/index.ts"],"sourcesContent":["export * from './use-entity-link-resolver.js';\n"],"names":[],"mappings":"AAAA,cAAc,gCAAgC"}
@@ -0,0 +1,17 @@
1
+ import { type ConfigurationRepositoryTag, createEntityLinkResolver, type EntityLinkRegistration, type EntityLinkResolverTag } from '@entifix/business';
2
+ import type { Context } from 'effect';
3
+ export type { EntityLinkRegistration };
4
+ export { createEntityLinkResolver };
5
+ /**
6
+ * React hook that memoizes an {@link EntityLinkResolverTag} context built from
7
+ * the given repository adapters. This is the page-level seam for last-mile
8
+ * wiring: a caller can register cached or state-backed adapters here without
9
+ * touching the base adapters or the use-case.
10
+ *
11
+ * The assembly itself is `createEntityLinkResolver` in
12
+ * `@entifix/business` — nothing about it is React, and a server
13
+ * component needs it without dragging this package's hooks along. Re-exported
14
+ * here so existing client call sites keep one import.
15
+ */
16
+ export declare function useEntityLinkResolver(configurationStore: Context.Context<ConfigurationRepositoryTag>, registrations: ReadonlyArray<EntityLinkRegistration>): Context.Context<EntityLinkResolverTag>;
17
+ //# sourceMappingURL=use-entity-link-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-entity-link-resolver.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-link-resolver/use-entity-link-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,0BAA0B,EAC/B,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,YAAY,EAAE,sBAAsB,EAAE,CAAC;AACvC,OAAO,EAAE,wBAAwB,EAAE,CAAC;AAEpC;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,kBAAkB,EAAE,OAAO,CAAC,OAAO,CAAC,0BAA0B,CAAC,EAC/D,aAAa,EAAE,aAAa,CAAC,sBAAsB,CAAC,GACnD,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAaxC"}
@@ -0,0 +1,26 @@
1
+ import { createEntityLinkResolver } from '@entifix/business';
2
+ import { useMemo } from 'react';
3
+ export { createEntityLinkResolver };
4
+ /**
5
+ * React hook that memoizes an {@link EntityLinkResolverTag} context built from
6
+ * the given repository adapters. This is the page-level seam for last-mile
7
+ * wiring: a caller can register cached or state-backed adapters here without
8
+ * touching the base adapters or the use-case.
9
+ *
10
+ * The assembly itself is `createEntityLinkResolver` in
11
+ * `@entifix/business` — nothing about it is React, and a server
12
+ * component needs it without dragging this package's hooks along. Re-exported
13
+ * here so existing client call sites keep one import.
14
+ */ export function useEntityLinkResolver(configurationStore, registrations) {
15
+ // Registrations are a static list per page; key the memo on the registered
16
+ // constructor names so it stays stable across renders without depending on the
17
+ // array literal's identity.
18
+ const registrationKey = registrations.map(([entityConstructor])=>entityConstructor.name).join('|');
19
+ return useMemo(()=>createEntityLinkResolver(configurationStore, registrations), // eslint-disable-next-line react-hooks/exhaustive-deps
20
+ [
21
+ configurationStore,
22
+ registrationKey
23
+ ]);
24
+ }
25
+
26
+ //# sourceMappingURL=use-entity-link-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/use-entity-link-resolver/use-entity-link-resolver.ts"],"sourcesContent":["import {\n type ConfigurationRepositoryTag,\n createEntityLinkResolver,\n type EntityLinkRegistration,\n type EntityLinkResolverTag,\n} from '@entifix/business';\nimport type { Context } from 'effect';\nimport { useMemo } from 'react';\n\nexport type { EntityLinkRegistration };\nexport { createEntityLinkResolver };\n\n/**\n * React hook that memoizes an {@link EntityLinkResolverTag} context built from\n * the given repository adapters. This is the page-level seam for last-mile\n * wiring: a caller can register cached or state-backed adapters here without\n * touching the base adapters or the use-case.\n *\n * The assembly itself is `createEntityLinkResolver` in\n * `@entifix/business` — nothing about it is React, and a server\n * component needs it without dragging this package's hooks along. Re-exported\n * here so existing client call sites keep one import.\n */\nexport function useEntityLinkResolver(\n configurationStore: Context.Context<ConfigurationRepositoryTag>,\n registrations: ReadonlyArray<EntityLinkRegistration>,\n): Context.Context<EntityLinkResolverTag> {\n // Registrations are a static list per page; key the memo on the registered\n // constructor names so it stays stable across renders without depending on the\n // array literal's identity.\n const registrationKey = registrations\n .map(([entityConstructor]) => entityConstructor.name)\n .join('|');\n\n return useMemo(\n () => createEntityLinkResolver(configurationStore, registrations),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [configurationStore, registrationKey],\n );\n}\n"],"names":["createEntityLinkResolver","useMemo","useEntityLinkResolver","configurationStore","registrations","registrationKey","map","entityConstructor","name","join"],"mappings":"AAAA,SAEEA,wBAAwB,QAGnB,oBAAoB;AAE3B,SAASC,OAAO,QAAQ,QAAQ;AAGhC,SAASD,wBAAwB,GAAG;AAEpC;;;;;;;;;;CAUC,GACD,OAAO,SAASE,sBACdC,kBAA+D,EAC/DC,aAAoD;IAEpD,2EAA2E;IAC3E,+EAA+E;IAC/E,4BAA4B;IAC5B,MAAMC,kBAAkBD,cACrBE,GAAG,CAAC,CAAC,CAACC,kBAAkB,GAAKA,kBAAkBC,IAAI,EACnDC,IAAI,CAAC;IAER,OAAOR,QACL,IAAMD,yBAAyBG,oBAAoBC,gBACnD,uDAAuD;IACvD;QAACD;QAAoBE;KAAgB;AAEzC"}
@@ -0,0 +1,3 @@
1
+ export * from './use-entity-link-source.js';
2
+ export * from './use-entity-link-source.types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-link-source/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mCAAmC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './use-entity-link-source.js';
2
+ export * from './use-entity-link-source.types.js';
3
+
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/use-entity-link-source/index.ts"],"sourcesContent":["export * from './use-entity-link-source.js';\nexport * from './use-entity-link-source.types.js';\n"],"names":[],"mappings":"AAAA,cAAc,8BAA8B;AAC5C,cAAc,oCAAoC"}
@@ -0,0 +1,25 @@
1
+ import { type Entity, type EntityLinkSource } from '@entifix/core';
2
+ import type { EntityLinkSourceConfig, UseEntityLinkSourceOptions } from './use-entity-link-source.types.js';
3
+ /**
4
+ * Produces the {@link EntityLinkSource} a link editor runs on: the quick
5
+ * type-ahead, the browse list, and the label of whatever key is currently held.
6
+ *
7
+ * This is the data half of the link mechanism. It exists as a hook — rather than
8
+ * inside the input — because `@entifix/react-controls` may not import this package
9
+ * (both are `entifix:react`, and the boundary rule forbids the sideways edge), so
10
+ * the two meet at the framework-free port declared in `@entifix/core`. A form
11
+ * wrapper calls it once per relation and hands the results to `EntityForm`'s
12
+ * `linkSources`.
13
+ *
14
+ * What may be picked is never decided here: the caller's `loadUc` (and optional
15
+ * `baseFiltering`) is what restricts the set, so a domain rule about assignable
16
+ * targets lives in a use-case, not in the UI.
17
+ *
18
+ * Three requests, each held back until it means something: the quick search runs
19
+ * a small page and re-runs on a debounced term; the browse list does not fetch at
20
+ * all until the dialog opens; and the label is only looked up when the caller has
21
+ * an id but no instance — a draft restored from storage — because a picked target
22
+ * already knows its own name.
23
+ */
24
+ export declare function useEntityLinkSource<TTarget extends Entity, TContext>(config: EntityLinkSourceConfig<TTarget, TContext>, { descriptor, selectedId, selectedEntity, }: UseEntityLinkSourceOptions<TTarget>): EntityLinkSource<TTarget>;
25
+ //# sourceMappingURL=use-entity-link-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-entity-link-source.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-link-source/use-entity-link-source.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,gBAAgB,EAGtB,MAAM,eAAe,CAAC;AAWvB,OAAO,KAAK,EACV,sBAAsB,EACtB,0BAA0B,EAC3B,MAAM,mCAAmC,CAAC;AAgC3C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,SAAS,MAAM,EAAE,QAAQ,EAClE,MAAM,EAAE,sBAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,EACjD,EACE,UAAU,EACV,UAAU,EACV,cAAc,GACf,EAAE,0BAA0B,CAAC,OAAO,CAAC,GACrC,gBAAgB,CAAC,OAAO,CAAC,CAuJ3B"}
@@ -0,0 +1,169 @@
1
+ 'use client';
2
+ import { _ as _extends } from "@swc/helpers/_/_extends";
3
+ import { EntityIdTag, EntityLoadRequestTag } from '@entifix/business';
4
+ import { describeEntityColumns, EntifixLogicError } from '@entifix/core';
5
+ import { useQuery } from '@tanstack/react-query';
6
+ import { Context, Effect } from 'effect';
7
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
+ import { combineFilterGroups } from '../../query/combine-filtering.js';
9
+ import { entityQueryKey, entityQueryScope } from '../../query/entity-query-key.js';
10
+ import { useDataLoading } from '../use-data-loading/index.js';
11
+ const DEFAULT_QUICK_PAGE_SIZE = 10;
12
+ const DEFAULT_DEBOUNCE_MS = 250;
13
+ /**
14
+ * Fails loudly on a search property the service will reject.
15
+ *
16
+ * A member's `filterable` metadata is also the server-side filter allowlist, so a
17
+ * quick search over a member that lacks it answers `400` — as an empty
18
+ * suggestion list, which reads to the user as "there are no brands". Better to
19
+ * break the developer's build than to lie to them.
20
+ */ function assertSearchable(config, searchProperty) {
21
+ const target = describeEntityColumns(config.entityConstructor).find((column)=>column.name === searchProperty);
22
+ if ((target == null ? void 0 : target.filterable) === true) return;
23
+ throw new EntifixLogicError(`Cannot search ${config.entityConstructor.name} by "${searchProperty}": ` + 'the member is not filterable, so the service would reject the query. ' + 'Declare it `filterable` on the target entity, or point the relation at ' + 'another member with `linkSearchProperty`.', undefined, {
24
+ entity: config.entityConstructor.name,
25
+ searchProperty
26
+ });
27
+ }
28
+ /**
29
+ * Produces the {@link EntityLinkSource} a link editor runs on: the quick
30
+ * type-ahead, the browse list, and the label of whatever key is currently held.
31
+ *
32
+ * This is the data half of the link mechanism. It exists as a hook — rather than
33
+ * inside the input — because `@entifix/react-controls` may not import this package
34
+ * (both are `entifix:react`, and the boundary rule forbids the sideways edge), so
35
+ * the two meet at the framework-free port declared in `@entifix/core`. A form
36
+ * wrapper calls it once per relation and hands the results to `EntityForm`'s
37
+ * `linkSources`.
38
+ *
39
+ * What may be picked is never decided here: the caller's `loadUc` (and optional
40
+ * `baseFiltering`) is what restricts the set, so a domain rule about assignable
41
+ * targets lives in a use-case, not in the UI.
42
+ *
43
+ * Three requests, each held back until it means something: the quick search runs
44
+ * a small page and re-runs on a debounced term; the browse list does not fetch at
45
+ * all until the dialog opens; and the label is only looked up when the caller has
46
+ * an id but no instance — a draft restored from storage — because a picked target
47
+ * already knows its own name.
48
+ */ export function useEntityLinkSource(config, { descriptor, selectedId, selectedEntity }) {
49
+ var _ref, _quickQuery_error;
50
+ var _quickQuery_data;
51
+ const { entityConstructor, loadUc, getUc, ctx, baseFiltering, quickPageSize = DEFAULT_QUICK_PAGE_SIZE, debounceMs = DEFAULT_DEBOUNCE_MS } = config;
52
+ const searchProperty = descriptor.linkSearchProperty;
53
+ assertSearchable(config, searchProperty);
54
+ // Callers rebuild `loadUc`/`getUc`/`ctx` inline on every render, so the latest
55
+ // are held in refs and kept out of the query keys — keying on their identity
56
+ // would refetch on every render. Same reason and same shape as `useDataLoading`.
57
+ const effects = useRef({
58
+ loadUc,
59
+ getUc,
60
+ ctx
61
+ });
62
+ useEffect(()=>{
63
+ effects.current = {
64
+ loadUc,
65
+ getUc,
66
+ ctx
67
+ };
68
+ });
69
+ const [term, setTerm] = useState('');
70
+ const [debouncedTerm, setDebouncedTerm] = useState('');
71
+ const [isOpen, setOpen] = useState(false);
72
+ // Typing is not a request per keystroke: the term settles first, and only the
73
+ // settled value reaches the service.
74
+ useEffect(()=>{
75
+ const timer = setTimeout(()=>setDebouncedTerm(term), debounceMs);
76
+ return ()=>clearTimeout(timer);
77
+ }, [
78
+ term,
79
+ debounceMs
80
+ ]);
81
+ const quickRequest = useMemo(()=>{
82
+ const filtering = combineFilterGroups(baseFiltering, debouncedTerm === '' ? undefined : {
83
+ operator: 'and',
84
+ values: [
85
+ {
86
+ property: searchProperty,
87
+ operator: 'like',
88
+ value: debouncedTerm
89
+ }
90
+ ]
91
+ });
92
+ const request = {
93
+ page: 1,
94
+ pageSize: quickPageSize
95
+ };
96
+ if (filtering) request.filtering = [
97
+ filtering
98
+ ];
99
+ return request;
100
+ }, [
101
+ baseFiltering,
102
+ debouncedTerm,
103
+ quickPageSize,
104
+ searchProperty
105
+ ]);
106
+ const quickQuery = useQuery({
107
+ queryKey: [
108
+ ...entityQueryKey(entityConstructor, quickRequest),
109
+ 'link-quick'
110
+ ],
111
+ queryFn: ()=>Effect.runPromise(Effect.provide(effects.current.loadUc, effects.current.ctx.pipe(Context.add(EntityLoadRequestTag, // The tag carries no generic, so the entity-typed request crosses
112
+ // it — the same cast `loadUCFactory` makes reading it back.
113
+ quickRequest))))
114
+ });
115
+ const browse = useDataLoading({
116
+ uc: loadUc,
117
+ ctx,
118
+ queryKey: entityQueryScope(entityConstructor),
119
+ baseFiltering,
120
+ enabled: isOpen
121
+ });
122
+ const needsLabel = selectedEntity === undefined && selectedId != null && getUc !== undefined;
123
+ const labelQuery = useQuery({
124
+ queryKey: [
125
+ ...entityQueryScope(entityConstructor),
126
+ 'link-label',
127
+ String(selectedId)
128
+ ],
129
+ enabled: needsLabel,
130
+ queryFn: ()=>Effect.runPromise(Effect.provide(// Guarded by `enabled`: the query only runs when both are present.
131
+ effects.current.getUc, effects.current.ctx.pipe(Context.add(EntityIdTag, selectedId))))
132
+ });
133
+ // A blank label is as useless as a missing one, so both fall back to the key —
134
+ // an unnamed row must still be distinguishable from the row above it.
135
+ const labelOf = useCallback((target)=>{
136
+ var _target_id;
137
+ const label = target[descriptor.linkLabelProperty];
138
+ return label == null || label === '' ? String((_target_id = target.id) != null ? _target_id : '') : String(label);
139
+ }, [
140
+ descriptor.linkLabelProperty
141
+ ]);
142
+ const resolved = selectedEntity != null ? selectedEntity : labelQuery.data;
143
+ return {
144
+ entityConstructor,
145
+ labelOf,
146
+ selected: {
147
+ label: resolved === undefined ? undefined : labelOf(resolved),
148
+ // The whole target, not only its name. The lookup above already fetched
149
+ // it, and a form restoring a draft has nothing else to rebuild an
150
+ // `embedded` relation from — the draft kept the id and lost the instance.
151
+ entity: resolved,
152
+ isLoading: needsLabel && labelQuery.isFetching
153
+ },
154
+ quick: {
155
+ term,
156
+ setTerm,
157
+ options: (_ref = (_quickQuery_data = quickQuery.data) == null ? void 0 : _quickQuery_data.items) != null ? _ref : [],
158
+ isLoading: quickQuery.isFetching,
159
+ error: (_quickQuery_error = quickQuery.error) != null ? _quickQuery_error : undefined
160
+ },
161
+ browse: _extends({}, browse, {
162
+ isOpen,
163
+ open: ()=>setOpen(true),
164
+ close: ()=>setOpen(false)
165
+ })
166
+ };
167
+ }
168
+
169
+ //# sourceMappingURL=use-entity-link-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/use-entity-link-source/use-entity-link-source.ts"],"sourcesContent":["'use client';\n\nimport { EntityIdTag, EntityLoadRequestTag } from '@entifix/business';\nimport {\n describeEntityColumns,\n type EntifixError,\n EntifixLogicError,\n type Entity,\n type EntityLinkSource,\n type EntityLoadRequest,\n type EntityPage,\n} from '@entifix/core';\nimport { useQuery } from '@tanstack/react-query';\nimport { Context, Effect } from 'effect';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { combineFilterGroups } from '../../query/combine-filtering.js';\nimport {\n entityQueryKey,\n entityQueryScope,\n} from '../../query/entity-query-key.js';\nimport { useDataLoading } from '../use-data-loading/index.js';\nimport type {\n EntityLinkSourceConfig,\n UseEntityLinkSourceOptions,\n} from './use-entity-link-source.types.js';\n\nconst DEFAULT_QUICK_PAGE_SIZE = 10;\nconst DEFAULT_DEBOUNCE_MS = 250;\n\n/**\n * Fails loudly on a search property the service will reject.\n *\n * A member's `filterable` metadata is also the server-side filter allowlist, so a\n * quick search over a member that lacks it answers `400` — as an empty\n * suggestion list, which reads to the user as \"there are no brands\". Better to\n * break the developer's build than to lie to them.\n */\nfunction assertSearchable<TTarget extends Entity>(\n config: EntityLinkSourceConfig<TTarget, unknown>,\n searchProperty: string,\n): void {\n const target = describeEntityColumns(config.entityConstructor).find(\n column => column.name === searchProperty,\n );\n if (target?.filterable === true) return;\n\n throw new EntifixLogicError(\n `Cannot search ${config.entityConstructor.name} by \"${searchProperty}\": ` +\n 'the member is not filterable, so the service would reject the query. ' +\n 'Declare it `filterable` on the target entity, or point the relation at ' +\n 'another member with `linkSearchProperty`.',\n undefined,\n { entity: config.entityConstructor.name, searchProperty },\n );\n}\n\n/**\n * Produces the {@link EntityLinkSource} a link editor runs on: the quick\n * type-ahead, the browse list, and the label of whatever key is currently held.\n *\n * This is the data half of the link mechanism. It exists as a hook — rather than\n * inside the input — because `@entifix/react-controls` may not import this package\n * (both are `entifix:react`, and the boundary rule forbids the sideways edge), so\n * the two meet at the framework-free port declared in `@entifix/core`. A form\n * wrapper calls it once per relation and hands the results to `EntityForm`'s\n * `linkSources`.\n *\n * What may be picked is never decided here: the caller's `loadUc` (and optional\n * `baseFiltering`) is what restricts the set, so a domain rule about assignable\n * targets lives in a use-case, not in the UI.\n *\n * Three requests, each held back until it means something: the quick search runs\n * a small page and re-runs on a debounced term; the browse list does not fetch at\n * all until the dialog opens; and the label is only looked up when the caller has\n * an id but no instance — a draft restored from storage — because a picked target\n * already knows its own name.\n */\nexport function useEntityLinkSource<TTarget extends Entity, TContext>(\n config: EntityLinkSourceConfig<TTarget, TContext>,\n {\n descriptor,\n selectedId,\n selectedEntity,\n }: UseEntityLinkSourceOptions<TTarget>,\n): EntityLinkSource<TTarget> {\n const {\n entityConstructor,\n loadUc,\n getUc,\n ctx,\n baseFiltering,\n quickPageSize = DEFAULT_QUICK_PAGE_SIZE,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n } = config;\n\n const searchProperty = descriptor.linkSearchProperty;\n assertSearchable(\n config as unknown as EntityLinkSourceConfig<TTarget, unknown>,\n searchProperty,\n );\n\n // Callers rebuild `loadUc`/`getUc`/`ctx` inline on every render, so the latest\n // are held in refs and kept out of the query keys — keying on their identity\n // would refetch on every render. Same reason and same shape as `useDataLoading`.\n const effects = useRef({ loadUc, getUc, ctx });\n useEffect(() => {\n effects.current = { loadUc, getUc, ctx };\n });\n\n const [term, setTerm] = useState('');\n const [debouncedTerm, setDebouncedTerm] = useState('');\n const [isOpen, setOpen] = useState(false);\n\n // Typing is not a request per keystroke: the term settles first, and only the\n // settled value reaches the service.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedTerm(term), debounceMs);\n return () => clearTimeout(timer);\n }, [term, debounceMs]);\n\n const quickRequest = useMemo<EntityLoadRequest<TTarget>>(() => {\n const filtering = combineFilterGroups<TTarget>(\n baseFiltering,\n debouncedTerm === ''\n ? undefined\n : {\n operator: 'and',\n values: [\n {\n property: searchProperty as keyof TTarget,\n operator: 'like',\n value: debouncedTerm,\n },\n ],\n },\n );\n\n const request: EntityLoadRequest<TTarget> = {\n page: 1,\n pageSize: quickPageSize,\n };\n if (filtering) request.filtering = [filtering];\n return request;\n }, [baseFiltering, debouncedTerm, quickPageSize, searchProperty]);\n\n const quickQuery = useQuery<EntityPage<TTarget>, EntifixError>({\n queryKey: [\n ...entityQueryKey(entityConstructor, quickRequest),\n 'link-quick',\n ],\n queryFn: () =>\n Effect.runPromise(\n Effect.provide(\n effects.current.loadUc,\n effects.current.ctx.pipe(\n Context.add(\n EntityLoadRequestTag,\n // The tag carries no generic, so the entity-typed request crosses\n // it — the same cast `loadUCFactory` makes reading it back.\n quickRequest as unknown as EntityLoadRequest,\n ),\n ),\n ),\n ),\n });\n\n const browse = useDataLoading<TTarget, TContext>({\n uc: loadUc,\n ctx,\n queryKey: entityQueryScope(entityConstructor),\n baseFiltering,\n enabled: isOpen,\n });\n\n const needsLabel =\n selectedEntity === undefined && selectedId != null && getUc !== undefined;\n\n const labelQuery = useQuery<TTarget, EntifixError>({\n queryKey: [\n ...entityQueryScope(entityConstructor),\n 'link-label',\n String(selectedId),\n ],\n enabled: needsLabel,\n queryFn: () =>\n Effect.runPromise(\n Effect.provide(\n // Guarded by `enabled`: the query only runs when both are present.\n effects.current.getUc as NonNullable<typeof getUc>,\n effects.current.ctx.pipe(Context.add(EntityIdTag, selectedId)),\n ),\n ),\n });\n\n // A blank label is as useless as a missing one, so both fall back to the key —\n // an unnamed row must still be distinguishable from the row above it.\n const labelOf = useCallback(\n (target: TTarget) => {\n const label = (target as Record<string, unknown>)[\n descriptor.linkLabelProperty\n ];\n return label == null || label === ''\n ? String(target.id ?? '')\n : String(label);\n },\n [descriptor.linkLabelProperty],\n );\n\n const resolved = selectedEntity ?? labelQuery.data;\n\n return {\n entityConstructor,\n labelOf,\n selected: {\n label: resolved === undefined ? undefined : labelOf(resolved),\n // The whole target, not only its name. The lookup above already fetched\n // it, and a form restoring a draft has nothing else to rebuild an\n // `embedded` relation from — the draft kept the id and lost the instance.\n entity: resolved,\n isLoading: needsLabel && labelQuery.isFetching,\n },\n quick: {\n term,\n setTerm,\n options: quickQuery.data?.items ?? [],\n isLoading: quickQuery.isFetching,\n error: quickQuery.error ?? undefined,\n },\n browse: {\n ...browse,\n isOpen,\n open: () => setOpen(true),\n close: () => setOpen(false),\n },\n };\n}\n"],"names":["EntityIdTag","EntityLoadRequestTag","describeEntityColumns","EntifixLogicError","useQuery","Context","Effect","useCallback","useEffect","useMemo","useRef","useState","combineFilterGroups","entityQueryKey","entityQueryScope","useDataLoading","DEFAULT_QUICK_PAGE_SIZE","DEFAULT_DEBOUNCE_MS","assertSearchable","config","searchProperty","target","entityConstructor","find","column","name","filterable","undefined","entity","useEntityLinkSource","descriptor","selectedId","selectedEntity","quickQuery","loadUc","getUc","ctx","baseFiltering","quickPageSize","debounceMs","linkSearchProperty","effects","current","term","setTerm","debouncedTerm","setDebouncedTerm","isOpen","setOpen","timer","setTimeout","clearTimeout","quickRequest","filtering","operator","values","property","value","request","page","pageSize","queryKey","queryFn","runPromise","provide","pipe","add","browse","uc","enabled","needsLabel","labelQuery","String","labelOf","label","linkLabelProperty","id","resolved","data","selected","isLoading","isFetching","quick","options","items","error","open","close"],"mappings":"AAAA;;AAEA,SAASA,WAAW,EAAEC,oBAAoB,QAAQ,oBAAoB;AACtE,SACEC,qBAAqB,EAErBC,iBAAiB,QAKZ,gBAAgB;AACvB,SAASC,QAAQ,QAAQ,wBAAwB;AACjD,SAASC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AACzC,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAQ;AAE1E,SAASC,mBAAmB,QAAQ,mCAAmC;AACvE,SACEC,cAAc,EACdC,gBAAgB,QACX,kCAAkC;AACzC,SAASC,cAAc,QAAQ,+BAA+B;AAM9D,MAAMC,0BAA0B;AAChC,MAAMC,sBAAsB;AAE5B;;;;;;;CAOC,GACD,SAASC,iBACPC,MAAgD,EAChDC,cAAsB;IAEtB,MAAMC,SAASnB,sBAAsBiB,OAAOG,iBAAiB,EAAEC,IAAI,CACjEC,CAAAA,SAAUA,OAAOC,IAAI,KAAKL;IAE5B,IAAIC,CAAAA,0BAAAA,OAAQK,UAAU,MAAK,MAAM;IAEjC,MAAM,IAAIvB,kBACR,CAAC,cAAc,EAAEgB,OAAOG,iBAAiB,CAACG,IAAI,CAAC,KAAK,EAAEL,eAAe,GAAG,CAAC,GACvE,0EACA,4EACA,6CACFO,WACA;QAAEC,QAAQT,OAAOG,iBAAiB,CAACG,IAAI;QAAEL;IAAe;AAE5D;AAEA;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASS,oBACdV,MAAiD,EACjD,EACEW,UAAU,EACVC,UAAU,EACVC,cAAc,EACsB;cA+I3BC;QAFEA;IA3Ib,MAAM,EACJX,iBAAiB,EACjBY,MAAM,EACNC,KAAK,EACLC,GAAG,EACHC,aAAa,EACbC,gBAAgBtB,uBAAuB,EACvCuB,aAAatB,mBAAmB,EACjC,GAAGE;IAEJ,MAAMC,iBAAiBU,WAAWU,kBAAkB;IACpDtB,iBACEC,QACAC;IAGF,+EAA+E;IAC/E,6EAA6E;IAC7E,iFAAiF;IACjF,MAAMqB,UAAU/B,OAAO;QAAEwB;QAAQC;QAAOC;IAAI;IAC5C5B,UAAU;QACRiC,QAAQC,OAAO,GAAG;YAAER;YAAQC;YAAOC;QAAI;IACzC;IAEA,MAAM,CAACO,MAAMC,QAAQ,GAAGjC,SAAS;IACjC,MAAM,CAACkC,eAAeC,iBAAiB,GAAGnC,SAAS;IACnD,MAAM,CAACoC,QAAQC,QAAQ,GAAGrC,SAAS;IAEnC,8EAA8E;IAC9E,qCAAqC;IACrCH,UAAU;QACR,MAAMyC,QAAQC,WAAW,IAAMJ,iBAAiBH,OAAOJ;QACvD,OAAO,IAAMY,aAAaF;IAC5B,GAAG;QAACN;QAAMJ;KAAW;IAErB,MAAMa,eAAe3C,QAAoC;QACvD,MAAM4C,YAAYzC,oBAChByB,eACAQ,kBAAkB,KACdlB,YACA;YACE2B,UAAU;YACVC,QAAQ;gBACN;oBACEC,UAAUpC;oBACVkC,UAAU;oBACVG,OAAOZ;gBACT;aACD;QACH;QAGN,MAAMa,UAAsC;YAC1CC,MAAM;YACNC,UAAUtB;QACZ;QACA,IAAIe,WAAWK,QAAQL,SAAS,GAAG;YAACA;SAAU;QAC9C,OAAOK;IACT,GAAG;QAACrB;QAAeQ;QAAeP;QAAelB;KAAe;IAEhE,MAAMa,aAAa7B,SAA4C;QAC7DyD,UAAU;eACLhD,eAAeS,mBAAmB8B;YACrC;SACD;QACDU,SAAS,IACPxD,OAAOyD,UAAU,CACfzD,OAAO0D,OAAO,CACZvB,QAAQC,OAAO,CAACR,MAAM,EACtBO,QAAQC,OAAO,CAACN,GAAG,CAAC6B,IAAI,CACtB5D,QAAQ6D,GAAG,CACTjE,sBACA,kEAAkE;YAClE,4DAA4D;YAC5DmD;IAKZ;IAEA,MAAMe,SAASpD,eAAkC;QAC/CqD,IAAIlC;QACJE;QACAyB,UAAU/C,iBAAiBQ;QAC3Be;QACAgC,SAAStB;IACX;IAEA,MAAMuB,aACJtC,mBAAmBL,aAAaI,cAAc,QAAQI,UAAUR;IAElE,MAAM4C,aAAanE,SAAgC;QACjDyD,UAAU;eACL/C,iBAAiBQ;YACpB;YACAkD,OAAOzC;SACR;QACDsC,SAASC;QACTR,SAAS,IACPxD,OAAOyD,UAAU,CACfzD,OAAO0D,OAAO,CACZ,mEAAmE;YACnEvB,QAAQC,OAAO,CAACP,KAAK,EACrBM,QAAQC,OAAO,CAACN,GAAG,CAAC6B,IAAI,CAAC5D,QAAQ6D,GAAG,CAAClE,aAAa+B;IAG1D;IAEA,+EAA+E;IAC/E,sEAAsE;IACtE,MAAM0C,UAAUlE,YACd,CAACc;YAKYA;QAJX,MAAMqD,QAAQ,AAACrD,MAAkC,CAC/CS,WAAW6C,iBAAiB,CAC7B;QACD,OAAOD,SAAS,QAAQA,UAAU,KAC9BF,QAAOnD,aAAAA,OAAOuD,EAAE,YAATvD,aAAa,MACpBmD,OAAOE;IACb,GACA;QAAC5C,WAAW6C,iBAAiB;KAAC;IAGhC,MAAME,WAAW7C,yBAAAA,iBAAkBuC,WAAWO,IAAI;IAElD,OAAO;QACLxD;QACAmD;QACAM,UAAU;YACRL,OAAOG,aAAalD,YAAYA,YAAY8C,QAAQI;YACpD,wEAAwE;YACxE,kEAAkE;YAClE,0EAA0E;YAC1EjD,QAAQiD;YACRG,WAAWV,cAAcC,WAAWU,UAAU;QAChD;QACAC,OAAO;YACLvC;YACAC;YACAuC,OAAO,WAAElD,mBAAAA,WAAW6C,IAAI,qBAAf7C,iBAAiBmD,KAAK,mBAAI,EAAE;YACrCJ,WAAW/C,WAAWgD,UAAU;YAChCI,KAAK,GAAEpD,oBAAAA,WAAWoD,KAAK,YAAhBpD,oBAAoBN;QAC7B;QACAwC,QAAQ,aACHA;YACHpB;YACAuC,MAAM,IAAMtC,QAAQ;YACpBuC,OAAO,IAAMvC,QAAQ;;IAEzB;AACF"}
@@ -0,0 +1,49 @@
1
+ import type { EntityIdTag, EntityLoadRequestTag } from '@entifix/business';
2
+ import type { EntifixError, Entity, EntityConstructor, EntityFieldDescriptor, EntityId, EntityPage, FilterGroup } from '@entifix/core';
3
+ import type { Context, Effect } from 'effect';
4
+ /**
5
+ * Where a picker's targets come from, and what may be picked.
6
+ *
7
+ * The use-cases are the parametrization seam: a domain that must restrict what is
8
+ * assignable supplies its own list use-case (only active brands, only leaf
9
+ * categories) instead of the generic `loadUCFactory`, and the picker never learns
10
+ * the rule. {@link baseFiltering} is the lighter version of the same idea for a
11
+ * restriction that needs no use-case of its own.
12
+ */
13
+ export interface EntityLinkSourceConfig<TTarget extends Entity, TContext> {
14
+ entityConstructor: EntityConstructor<TTarget>;
15
+ /** Lists the targets. Supplies {@link EntityLoadRequestTag} per request. */
16
+ loadUc: Effect.Effect<EntityPage<TTarget>, EntifixError, EntityLoadRequestTag | TContext>;
17
+ /**
18
+ * Loads one target by id — how a draft restored from storage turns its held
19
+ * foreign key back into a name. Omit it and the input falls back to showing
20
+ * the bare key.
21
+ */
22
+ getUc?: Effect.Effect<TTarget, EntifixError, EntityIdTag | TContext>;
23
+ ctx: Context.Context<TContext>;
24
+ /**
25
+ * ANDed into every quick and browse request, so a caller can narrow the
26
+ * selectable set without writing a use-case.
27
+ */
28
+ baseFiltering?: FilterGroup<TTarget>;
29
+ /** How many suggestions the quick search asks for. Defaults to 10. */
30
+ quickPageSize?: number;
31
+ /** Milliseconds of quiet before a typed term is sent. Defaults to 250. */
32
+ debounceMs?: number;
33
+ }
34
+ export interface UseEntityLinkSourceOptions<TTarget extends Entity> {
35
+ /**
36
+ * The relation being edited. Its `linkSearchProperty` is what the quick search
37
+ * filters on and its `linkLabelProperty` is what a target reads as.
38
+ */
39
+ descriptor: EntityFieldDescriptor;
40
+ /** The foreign key the draft currently holds. */
41
+ selectedId?: EntityId;
42
+ /**
43
+ * The instance behind {@link selectedId} when the caller already has it — the
44
+ * one the user just picked, or the one a loaded record carried. Present, it
45
+ * spares the label request entirely.
46
+ */
47
+ selectedEntity?: TTarget;
48
+ }
49
+ //# sourceMappingURL=use-entity-link-source.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-entity-link-source.types.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-link-source/use-entity-link-source.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,iBAAiB,EACjB,qBAAqB,EACrB,QAAQ,EACR,UAAU,EACV,WAAW,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO,SAAS,MAAM,EAAE,QAAQ;IACtE,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC9C,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC,MAAM,CACnB,UAAU,CAAC,OAAO,CAAC,EACnB,YAAY,EACZ,oBAAoB,GAAG,QAAQ,CAChC,CAAC;IACF;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC;IACrE,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B;;;OAGG;IACH,aAAa,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B,CAAC,OAAO,SAAS,MAAM;IAChE;;;OAGG;IACH,UAAU,EAAE,qBAAqB,CAAC;IAClC,iDAAiD;IACjD,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B"}
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=use-entity-link-source.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/use-entity-link-source/use-entity-link-source.types.ts"],"sourcesContent":["import type { EntityIdTag, EntityLoadRequestTag } from '@entifix/business';\nimport type {\n EntifixError,\n Entity,\n EntityConstructor,\n EntityFieldDescriptor,\n EntityId,\n EntityPage,\n FilterGroup,\n} from '@entifix/core';\nimport type { Context, Effect } from 'effect';\n\n/**\n * Where a picker's targets come from, and what may be picked.\n *\n * The use-cases are the parametrization seam: a domain that must restrict what is\n * assignable supplies its own list use-case (only active brands, only leaf\n * categories) instead of the generic `loadUCFactory`, and the picker never learns\n * the rule. {@link baseFiltering} is the lighter version of the same idea for a\n * restriction that needs no use-case of its own.\n */\nexport interface EntityLinkSourceConfig<TTarget extends Entity, TContext> {\n entityConstructor: EntityConstructor<TTarget>;\n /** Lists the targets. Supplies {@link EntityLoadRequestTag} per request. */\n loadUc: Effect.Effect<\n EntityPage<TTarget>,\n EntifixError,\n EntityLoadRequestTag | TContext\n >;\n /**\n * Loads one target by id — how a draft restored from storage turns its held\n * foreign key back into a name. Omit it and the input falls back to showing\n * the bare key.\n */\n getUc?: Effect.Effect<TTarget, EntifixError, EntityIdTag | TContext>;\n ctx: Context.Context<TContext>;\n /**\n * ANDed into every quick and browse request, so a caller can narrow the\n * selectable set without writing a use-case.\n */\n baseFiltering?: FilterGroup<TTarget>;\n /** How many suggestions the quick search asks for. Defaults to 10. */\n quickPageSize?: number;\n /** Milliseconds of quiet before a typed term is sent. Defaults to 250. */\n debounceMs?: number;\n}\n\nexport interface UseEntityLinkSourceOptions<TTarget extends Entity> {\n /**\n * The relation being edited. Its `linkSearchProperty` is what the quick search\n * filters on and its `linkLabelProperty` is what a target reads as.\n */\n descriptor: EntityFieldDescriptor;\n /** The foreign key the draft currently holds. */\n selectedId?: EntityId;\n /**\n * The instance behind {@link selectedId} when the caller already has it — the\n * one the user just picked, or the one a loaded record carried. Present, it\n * spares the label request entirely.\n */\n selectedEntity?: TTarget;\n}\n"],"names":[],"mappings":"AA+CA,WAcC"}
@@ -0,0 +1,3 @@
1
+ export * from './use-entity-mutation.js';
2
+ export * from './use-entity-mutation.types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-mutation/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './use-entity-mutation.js';
2
+ export * from './use-entity-mutation.types.js';
3
+
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/use-entity-mutation/index.ts"],"sourcesContent":["export * from './use-entity-mutation.js';\nexport * from './use-entity-mutation.types.js';\n"],"names":[],"mappings":"AAAA,cAAc,2BAA2B;AACzC,cAAc,iCAAiC"}