@stapel/listings-react 0.1.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 (225) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/MODULE.md +191 -0
  3. package/README.md +169 -0
  4. package/dist/api/generated/schema.d.ts +1253 -0
  5. package/dist/api/generated/schema.d.ts.map +1 -0
  6. package/dist/api/generated/schema.js +2 -0
  7. package/dist/api/generated/schema.js.map +1 -0
  8. package/dist/api/listingsApi.d.ts +145 -0
  9. package/dist/api/listingsApi.d.ts.map +1 -0
  10. package/dist/api/listingsApi.js +38 -0
  11. package/dist/api/listingsApi.js.map +1 -0
  12. package/dist/api/types.d.ts +156 -0
  13. package/dist/api/types.d.ts.map +1 -0
  14. package/dist/api/types.js +37 -0
  15. package/dist/api/types.js.map +1 -0
  16. package/dist/default/ErrorAlert.d.ts +9 -0
  17. package/dist/default/ErrorAlert.d.ts.map +1 -0
  18. package/dist/default/ErrorAlert.js +27 -0
  19. package/dist/default/ErrorAlert.js.map +1 -0
  20. package/dist/default/FavoritesPane.d.ts +18 -0
  21. package/dist/default/FavoritesPane.d.ts.map +1 -0
  22. package/dist/default/FavoritesPane.js +28 -0
  23. package/dist/default/FavoritesPane.js.map +1 -0
  24. package/dist/default/ListingCard.d.ts +42 -0
  25. package/dist/default/ListingCard.d.ts.map +1 -0
  26. package/dist/default/ListingCard.js +37 -0
  27. package/dist/default/ListingCard.js.map +1 -0
  28. package/dist/default/ListingComposerPage.d.ts +46 -0
  29. package/dist/default/ListingComposerPage.d.ts.map +1 -0
  30. package/dist/default/ListingComposerPage.js +99 -0
  31. package/dist/default/ListingComposerPage.js.map +1 -0
  32. package/dist/default/ListingDetailPane.d.ts +33 -0
  33. package/dist/default/ListingDetailPane.d.ts.map +1 -0
  34. package/dist/default/ListingDetailPane.js +48 -0
  35. package/dist/default/ListingDetailPane.js.map +1 -0
  36. package/dist/default/ListingPhoto.d.ts +25 -0
  37. package/dist/default/ListingPhoto.d.ts.map +1 -0
  38. package/dist/default/ListingPhoto.js +20 -0
  39. package/dist/default/ListingPhoto.js.map +1 -0
  40. package/dist/default/MyListingsPane.d.ts +25 -0
  41. package/dist/default/MyListingsPane.d.ts.map +1 -0
  42. package/dist/default/MyListingsPane.js +84 -0
  43. package/dist/default/MyListingsPane.js.map +1 -0
  44. package/dist/default/StatusTags.d.ts +31 -0
  45. package/dist/default/StatusTags.d.ts.map +1 -0
  46. package/dist/default/StatusTags.js +40 -0
  47. package/dist/default/StatusTags.js.map +1 -0
  48. package/dist/default/icons.d.ts +16 -0
  49. package/dist/default/icons.d.ts.map +1 -0
  50. package/dist/default/icons.js +5 -0
  51. package/dist/default/icons.js.map +1 -0
  52. package/dist/default/index.d.ts +35 -0
  53. package/dist/default/index.d.ts.map +1 -0
  54. package/dist/default/index.js +26 -0
  55. package/dist/default/index.js.map +1 -0
  56. package/dist/default/theme.d.ts +25 -0
  57. package/dist/default/theme.d.ts.map +1 -0
  58. package/dist/default/theme.js +40 -0
  59. package/dist/default/theme.js.map +1 -0
  60. package/dist/default/types.d.ts +12 -0
  61. package/dist/default/types.d.ts.map +1 -0
  62. package/dist/default/types.js +2 -0
  63. package/dist/default/types.js.map +1 -0
  64. package/dist/flows/registry.d.ts +35 -0
  65. package/dist/flows/registry.d.ts.map +1 -0
  66. package/dist/flows/registry.js +33 -0
  67. package/dist/flows/registry.js.map +1 -0
  68. package/dist/headless/Favorites.d.ts +52 -0
  69. package/dist/headless/Favorites.d.ts.map +1 -0
  70. package/dist/headless/Favorites.js +78 -0
  71. package/dist/headless/Favorites.js.map +1 -0
  72. package/dist/headless/ListingActions.d.ts +30 -0
  73. package/dist/headless/ListingActions.d.ts.map +1 -0
  74. package/dist/headless/ListingActions.js +54 -0
  75. package/dist/headless/ListingActions.js.map +1 -0
  76. package/dist/headless/ListingComposer.d.ts +133 -0
  77. package/dist/headless/ListingComposer.d.ts.map +1 -0
  78. package/dist/headless/ListingComposer.js +246 -0
  79. package/dist/headless/ListingComposer.js.map +1 -0
  80. package/dist/headless/ListingDetail.d.ts +81 -0
  81. package/dist/headless/ListingDetail.d.ts.map +1 -0
  82. package/dist/headless/ListingDetail.js +101 -0
  83. package/dist/headless/ListingDetail.js.map +1 -0
  84. package/dist/headless/ListingsProvider.d.ts +17 -0
  85. package/dist/headless/ListingsProvider.d.ts.map +1 -0
  86. package/dist/headless/ListingsProvider.js +16 -0
  87. package/dist/headless/ListingsProvider.js.map +1 -0
  88. package/dist/headless/MyListings.d.ts +44 -0
  89. package/dist/headless/MyListings.d.ts.map +1 -0
  90. package/dist/headless/MyListings.js +82 -0
  91. package/dist/headless/MyListings.js.map +1 -0
  92. package/dist/headless/useMandateGate.d.ts +28 -0
  93. package/dist/headless/useMandateGate.d.ts.map +1 -0
  94. package/dist/headless/useMandateGate.js +39 -0
  95. package/dist/headless/useMandateGate.js.map +1 -0
  96. package/dist/i18n/errorsMap.d.ts +34 -0
  97. package/dist/i18n/errorsMap.d.ts.map +1 -0
  98. package/dist/i18n/errorsMap.js +45 -0
  99. package/dist/i18n/errorsMap.js.map +1 -0
  100. package/dist/i18n/es.d.ts +21 -0
  101. package/dist/i18n/es.d.ts.map +1 -0
  102. package/dist/i18n/es.js +146 -0
  103. package/dist/i18n/es.js.map +1 -0
  104. package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
  105. package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
  106. package/dist/i18n/generated/errors.es.gen.js +58 -0
  107. package/dist/i18n/generated/errors.es.gen.js.map +1 -0
  108. package/dist/i18n/generated/errors.gen.d.ts +413 -0
  109. package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
  110. package/dist/i18n/generated/errors.gen.js +210 -0
  111. package/dist/i18n/generated/errors.gen.js.map +1 -0
  112. package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
  113. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
  114. package/dist/i18n/generated/errors.ru.gen.js +58 -0
  115. package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
  116. package/dist/i18n/keys.d.ts +170 -0
  117. package/dist/i18n/keys.d.ts.map +1 -0
  118. package/dist/i18n/keys.js +295 -0
  119. package/dist/i18n/keys.js.map +1 -0
  120. package/dist/i18n/ru.d.ts +37 -0
  121. package/dist/i18n/ru.d.ts.map +1 -0
  122. package/dist/i18n/ru.js +162 -0
  123. package/dist/i18n/ru.js.map +1 -0
  124. package/dist/index.d.ts +103 -0
  125. package/dist/index.d.ts.map +1 -0
  126. package/dist/index.js +98 -0
  127. package/dist/index.js.map +1 -0
  128. package/dist/model/context.d.ts +11 -0
  129. package/dist/model/context.d.ts.map +1 -0
  130. package/dist/model/context.js +16 -0
  131. package/dist/model/context.js.map +1 -0
  132. package/dist/model/draft.d.ts +138 -0
  133. package/dist/model/draft.d.ts.map +1 -0
  134. package/dist/model/draft.js +192 -0
  135. package/dist/model/draft.js.map +1 -0
  136. package/dist/model/features.d.ts +69 -0
  137. package/dist/model/features.d.ts.map +1 -0
  138. package/dist/model/features.js +73 -0
  139. package/dist/model/features.js.map +1 -0
  140. package/dist/model/mineSource.d.ts +47 -0
  141. package/dist/model/mineSource.d.ts.map +1 -0
  142. package/dist/model/mineSource.js +45 -0
  143. package/dist/model/mineSource.js.map +1 -0
  144. package/dist/model/mutations.d.ts +60 -0
  145. package/dist/model/mutations.d.ts.map +1 -0
  146. package/dist/model/mutations.js +165 -0
  147. package/dist/model/mutations.js.map +1 -0
  148. package/dist/model/queries.d.ts +75 -0
  149. package/dist/model/queries.d.ts.map +1 -0
  150. package/dist/model/queries.js +117 -0
  151. package/dist/model/queries.js.map +1 -0
  152. package/dist/model/queryKeys.d.ts +50 -0
  153. package/dist/model/queryKeys.d.ts.map +1 -0
  154. package/dist/model/queryKeys.js +24 -0
  155. package/dist/model/queryKeys.js.map +1 -0
  156. package/dist/model/runtime.d.ts +69 -0
  157. package/dist/model/runtime.d.ts.map +1 -0
  158. package/dist/model/runtime.js +14 -0
  159. package/dist/model/runtime.js.map +1 -0
  160. package/dist/model/status.d.ts +117 -0
  161. package/dist/model/status.d.ts.map +1 -0
  162. package/dist/model/status.js +164 -0
  163. package/dist/model/status.js.map +1 -0
  164. package/dist/model/transitions.d.ts +41 -0
  165. package/dist/model/transitions.d.ts.map +1 -0
  166. package/dist/model/transitions.js +25 -0
  167. package/dist/model/transitions.js.map +1 -0
  168. package/dist/model/validation.d.ts +135 -0
  169. package/dist/model/validation.d.ts.map +1 -0
  170. package/dist/model/validation.js +191 -0
  171. package/dist/model/validation.js.map +1 -0
  172. package/dist/nav/manifest.d.ts +46 -0
  173. package/dist/nav/manifest.d.ts.map +1 -0
  174. package/dist/nav/manifest.js +56 -0
  175. package/dist/nav/manifest.js.map +1 -0
  176. package/llms.txt +118 -0
  177. package/manifest.json +1044 -0
  178. package/nav-manifest.json +84 -0
  179. package/package.json +121 -0
  180. package/src/analytics/generated/events.json +7 -0
  181. package/src/api/generated/schema.ts +1255 -0
  182. package/src/api/listingsApi.ts +237 -0
  183. package/src/api/types.ts +193 -0
  184. package/src/default/ErrorAlert.tsx +46 -0
  185. package/src/default/FavoritesPane.tsx +123 -0
  186. package/src/default/ListingCard.tsx +178 -0
  187. package/src/default/ListingComposerPage.tsx +466 -0
  188. package/src/default/ListingDetailPane.tsx +271 -0
  189. package/src/default/ListingPhoto.tsx +62 -0
  190. package/src/default/MyListingsPane.tsx +333 -0
  191. package/src/default/StatusTags.tsx +86 -0
  192. package/src/default/icons.tsx +31 -0
  193. package/src/default/index.ts +35 -0
  194. package/src/default/theme.tsx +67 -0
  195. package/src/default/types.ts +12 -0
  196. package/src/flows/registry.ts +45 -0
  197. package/src/headless/Favorites.tsx +137 -0
  198. package/src/headless/ListingActions.tsx +101 -0
  199. package/src/headless/ListingComposer.tsx +476 -0
  200. package/src/headless/ListingDetail.tsx +231 -0
  201. package/src/headless/ListingsProvider.tsx +20 -0
  202. package/src/headless/MyListings.tsx +148 -0
  203. package/src/headless/useMandateGate.ts +40 -0
  204. package/src/i18n/errorsMap.ts +62 -0
  205. package/src/i18n/es.ts +198 -0
  206. package/src/i18n/generated/errors.es.gen.ts +64 -0
  207. package/src/i18n/generated/errors.gen.ts +241 -0
  208. package/src/i18n/generated/errors.json +499 -0
  209. package/src/i18n/generated/errors.ru.gen.ts +64 -0
  210. package/src/i18n/keys.ts +356 -0
  211. package/src/i18n/ru.ts +215 -0
  212. package/src/index.ts +260 -0
  213. package/src/model/context.tsx +28 -0
  214. package/src/model/draft.ts +307 -0
  215. package/src/model/features.ts +122 -0
  216. package/src/model/mineSource.ts +55 -0
  217. package/src/model/mutations.ts +224 -0
  218. package/src/model/queries.ts +150 -0
  219. package/src/model/queryKeys.ts +76 -0
  220. package/src/model/runtime.ts +87 -0
  221. package/src/model/status.ts +279 -0
  222. package/src/model/transitions.ts +62 -0
  223. package/src/model/validation.ts +271 -0
  224. package/src/nav/manifest.ts +97 -0
  225. package/tsconfig.json +26 -0
@@ -0,0 +1,165 @@
1
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import { useListingsApi } from "./context.js";
3
+ import { listingsQueryKeys } from "./queryKeys.js";
4
+ /**
5
+ * Every write in the module, each one saying what it invalidates and why.
6
+ *
7
+ * ── The listing id is a VARIABLE, not a hook argument ──────────────────────
8
+ *
9
+ * Every mutation here takes its id in the mutation's variables rather than in
10
+ * the hook's closure, and that is a correctness decision rather than a style
11
+ * one. The composer creates the draft row and saves into it IN THE SAME user
12
+ * gesture: an id-bound hook would still be holding the id from the render
13
+ * before the create, so the save would go to the wrong path (or to none). A
14
+ * variable is read at call time, which is the only time the id is known.
15
+ *
16
+ * ── The invalidation rule ──────────────────────────────────────────────────
17
+ *
18
+ * A write that can move `status` or `moderation_status` invalidates the
19
+ * DETAIL, the STATUS probe and the COUNTERS — the counters because their
20
+ * three tabs are defined by status sets (`views.my_counters`), so a publish
21
+ * silently moves a row from "drafts" to "active" and a dashboard that did not
22
+ * refetch would show the old number beside the new row.
23
+ *
24
+ * Nothing here is optimistic except the favourite toggle, and that exception
25
+ * is argued at its own hook.
26
+ */
27
+ /** Everything a status-moving write must freshen. */
28
+ function invalidateListing(queryClient, id) {
29
+ void queryClient.invalidateQueries({ queryKey: listingsQueryKeys.detail(id) });
30
+ void queryClient.invalidateQueries({ queryKey: listingsQueryKeys.status(id) });
31
+ void queryClient.invalidateQueries({
32
+ queryKey: listingsQueryKeys.myCounters(),
33
+ });
34
+ void queryClient.invalidateQueries({
35
+ queryKey: listingsQueryKeys.validateDraft(id),
36
+ });
37
+ }
38
+ /** Start a draft. The server forces `owner` and `status=draft`. */
39
+ export function useCreateDraft() {
40
+ const api = useListingsApi();
41
+ const queryClient = useQueryClient();
42
+ return useMutation({
43
+ mutationFn: (body) => api.createDraft(body),
44
+ retry: false,
45
+ onSuccess: () => {
46
+ void queryClient.invalidateQueries({
47
+ queryKey: listingsQueryKeys.myCounters(),
48
+ });
49
+ },
50
+ });
51
+ }
52
+ /**
53
+ * Persist draft fields. Always partial — send what the form holds.
54
+ *
55
+ * The validate-draft entry is invalidated rather than written: the saved row
56
+ * comes back, but it carries no verdict, and a stale verdict beside fresh
57
+ * content is the one combination a composer must never show.
58
+ */
59
+ export function useSaveDraft() {
60
+ const api = useListingsApi();
61
+ const queryClient = useQueryClient();
62
+ return useMutation({
63
+ mutationFn: (input) => api.saveDraft(input.id, input.body),
64
+ retry: false,
65
+ onSuccess: (_data, input) => {
66
+ invalidateListing(queryClient, input.id);
67
+ },
68
+ });
69
+ }
70
+ /**
71
+ * Promote the draft.
72
+ *
73
+ * `retry: false` — the 400 here is a VERDICT about the content (a bare
74
+ * `ValidationBatchResult` body, see `model/validation.ts`), and repeating a
75
+ * verdict three times only delays the moment the composer can lay it on the
76
+ * controls.
77
+ */
78
+ export function usePublishListing() {
79
+ const api = useListingsApi();
80
+ const queryClient = useQueryClient();
81
+ return useMutation({
82
+ mutationFn: (id) => api.publish(id),
83
+ retry: false,
84
+ onSuccess: (_data, id) => {
85
+ invalidateListing(queryClient, id);
86
+ },
87
+ });
88
+ }
89
+ /** Move to ARCHIVED. 409 `error.409.invalid_listing_transition` carries
90
+ * `params.from_status`, which is what turns the refusal into a sentence. */
91
+ export function useArchiveListing() {
92
+ const api = useListingsApi();
93
+ const queryClient = useQueryClient();
94
+ return useMutation({
95
+ mutationFn: (id) => api.archive(id),
96
+ retry: false,
97
+ onSuccess: (_data, id) => {
98
+ invalidateListing(queryClient, id);
99
+ },
100
+ });
101
+ }
102
+ /** Mark SOLD. Same 409 contract as {@link useArchiveListing}. */
103
+ export function useCompleteListing() {
104
+ const api = useListingsApi();
105
+ const queryClient = useQueryClient();
106
+ return useMutation({
107
+ mutationFn: (id) => api.complete(id),
108
+ retry: false,
109
+ onSuccess: (_data, id) => {
110
+ invalidateListing(queryClient, id);
111
+ },
112
+ });
113
+ }
114
+ /** Soft-delete. Refused with `error.409.listing_cannot_delete_active` while
115
+ * the listing is PUBLISHED or PENDING. */
116
+ export function useDeleteListing() {
117
+ const api = useListingsApi();
118
+ const queryClient = useQueryClient();
119
+ return useMutation({
120
+ mutationFn: (id) => api.remove(id),
121
+ retry: false,
122
+ onSuccess: (_data, id) => {
123
+ invalidateListing(queryClient, id);
124
+ void queryClient.invalidateQueries({ queryKey: listingsQueryKeys.all });
125
+ },
126
+ });
127
+ }
128
+ /**
129
+ * Favourite / un-favourite, as ONE mutation over the intended next state.
130
+ *
131
+ * Two endpoints, one intent — and modelling it as two hooks would let a
132
+ * caller call the wrong one for the state it is in. The server is idempotent
133
+ * on both sides (`get_or_create` / `filter().delete()`), so a double-click
134
+ * cannot desynchronise anything.
135
+ *
136
+ * This is the pair's ONE write whose next state the client may predict, and
137
+ * the reason is that the truth is a single boolean the caller already holds.
138
+ * Everything else here moves a LIFECYCLE whose next state is the server's to
139
+ * decide — guessing `pending` versus `published` after a publish would be
140
+ * inventing the very verdict the pair exists to report faithfully.
141
+ *
142
+ * The invalidation reaches the favourites LIST as well as the row: a heart
143
+ * turned off on the favourites page must remove the card, not leave a hollow
144
+ * one behind.
145
+ */
146
+ export function useFavoriteListing() {
147
+ const api = useListingsApi();
148
+ const queryClient = useQueryClient();
149
+ return useMutation({
150
+ mutationFn: (input) => input.favorited ? api.favorite(input.id) : api.unfavorite(input.id),
151
+ retry: false,
152
+ onSettled: (_data, _error, input) => {
153
+ void queryClient.invalidateQueries({
154
+ queryKey: listingsQueryKeys.detail(input.id),
155
+ });
156
+ void queryClient.invalidateQueries({
157
+ queryKey: listingsQueryKeys.allFavorites(),
158
+ });
159
+ void queryClient.invalidateQueries({
160
+ queryKey: listingsQueryKeys.allLists(),
161
+ });
162
+ },
163
+ });
164
+ }
165
+ //# sourceMappingURL=mutations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutations.js","sourceRoot":"","sources":["../../src/model/mutations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAWpE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,qDAAqD;AACrD,SAAS,iBAAiB,CAAC,WAAwB,EAAE,EAAU;IAC7D,KAAK,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/E,KAAK,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/E,KAAK,WAAW,CAAC,iBAAiB,CAAC;QACjC,QAAQ,EAAE,iBAAiB,CAAC,UAAU,EAAE;KACzC,CAAC,CAAC;IACH,KAAK,WAAW,CAAC,iBAAiB,CAAC;QACjC,QAAQ,EAAE,iBAAiB,CAAC,aAAa,CAAC,EAAE,CAAC;KAC9C,CAAC,CAAC;AACL,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc;IAK5B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,CAAC,IAAuB,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC;QAC9D,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,GAAG,EAAE;YACd,KAAK,WAAW,CAAC,iBAAiB,CAAC;gBACjC,QAAQ,EAAE,iBAAiB,CAAC,UAAU,EAAE;aACzC,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,YAAY;IAK1B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,CAAC,KAAqB,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC;QAC1E,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC1B,iBAAiB,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3C,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB;IAK/B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YACvB,iBAAiB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACrC,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;4EAC4E;AAC5E,MAAM,UAAU,iBAAiB;IAK/B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YACvB,iBAAiB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACrC,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,kBAAkB;IAKhC,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5C,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YACvB,iBAAiB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACrC,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;0CAC0C;AAC1C,MAAM,UAAU,gBAAgB;IAK9B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YACvB,iBAAiB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YACnC,KAAK,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,GAAG,EAAE,CAAC,CAAC;QAC1E,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAQD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB;IAKhC,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,CAAC,KAAoB,EAAE,EAAE,CACnC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACrE,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;YAClC,KAAK,WAAW,CAAC,iBAAiB,CAAC;gBACjC,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;aAC7C,CAAC,CAAC;YACH,KAAK,WAAW,CAAC,iBAAiB,CAAC;gBACjC,QAAQ,EAAE,iBAAiB,CAAC,YAAY,EAAE;aAC3C,CAAC,CAAC;YACH,KAAK,WAAW,CAAC,iBAAiB,CAAC;gBACjC,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,EAAE;aACvC,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,75 @@
1
+ import type { UseQueryResult } from "@tanstack/react-query";
2
+ import type { StapelApiError } from "@stapel/core";
3
+ import type { ValidationBatchResult } from "@stapel/attributes-react";
4
+ import type { ListingDetail, ListingPageParams, ListingStatusInfo, MyCounters, PaginatedListingCards } from "../api/types.js";
5
+ /**
6
+ * Read hooks over the listings API (frontend-standard §2). Keys are
7
+ * namespaced (see `listingsQueryKeys`).
8
+ *
9
+ * ── Two gating regimes in one module, and the line between them ────────────
10
+ *
11
+ * `useListing` / `useListingStatus` / `useListingCards` call
12
+ * `IsAuthenticatedOrReadOnly` and `AllowAny` endpoints: a visitor reads them
13
+ * with no session at all, so gating on `useActiveSessionReady` would make a
14
+ * shop window wait for a login bootstrap it has no stake in. Core's own doc
15
+ * comment carves out exactly this case.
16
+ *
17
+ * `useMyCounters` / `useMyFavorites` / `useValidateDraft` are
18
+ * `IsAuthenticated` and DO gate: firing them before the session substrate has
19
+ * settled produces a 401 that means "we asked too early", which is
20
+ * indistinguishable on screen from "you are signed out".
21
+ */
22
+ /**
23
+ * One listing in full.
24
+ *
25
+ * `retry: false` on purpose. The failures here are verdicts about the
26
+ * request: a 404 for a listing that is gone, a 403 for one that is not
27
+ * yours. Retrying a 404 three times only delays the moment the page can say
28
+ * which it was — and for a soft-deleted listing, `useListingStatus` is the
29
+ * read that can actually explain it (see `headless/ListingDetail.tsx`).
30
+ */
31
+ export declare function useListing(id: number | undefined, options?: {
32
+ readonly enabled?: boolean;
33
+ }): UseQueryResult<ListingDetail, StapelApiError>;
34
+ /**
35
+ * The status probe — both axes plus `is_deleted`, for ANY listing id.
36
+ *
37
+ * The one read that survives a soft delete (`Listing.all_objects`), which is
38
+ * why a detail page runs it beside the detail rather than only after a
39
+ * failure: "this listing was removed" is a sentence, and a bare 404 is not.
40
+ */
41
+ export declare function useListingStatus(id: number | undefined, options?: {
42
+ readonly enabled?: boolean;
43
+ }): UseQueryResult<ListingStatusInfo, StapelApiError>;
44
+ /**
45
+ * A keyset page of PUBLISHED cards.
46
+ *
47
+ * `keepPreviousData`: paging a grid that blanks between pages reads as a
48
+ * failure, and the previous page is still a true answer about the previous
49
+ * cursor. The FIRST load has nothing to keep and is `loading`; a failure is
50
+ * still `failed` rather than stale rows pretending to be current.
51
+ */
52
+ export declare function useListingCards(params?: ListingPageParams, options?: {
53
+ readonly enabled?: boolean;
54
+ }): UseQueryResult<PaginatedListingCards, StapelApiError>;
55
+ /** The dashboard's three counts. Session-gated. */
56
+ export declare function useMyCounters(options?: {
57
+ readonly enabled?: boolean;
58
+ }): UseQueryResult<MyCounters, StapelApiError>;
59
+ /** A keyset page of the caller's favourites. Session-gated. */
60
+ export declare function useMyFavorites(params?: ListingPageParams, options?: {
61
+ readonly enabled?: boolean;
62
+ }): UseQueryResult<PaginatedListingCards, StapelApiError>;
63
+ /**
64
+ * What publishing WOULD say, without publishing.
65
+ *
66
+ * Disabled by default: it is a round trip per call and the composer's own
67
+ * mirror answers the same question for free most of the time. A composer
68
+ * turns it on when the person asks ("check my listing") and after a save, so
69
+ * the SERVER's opinion of the saved draft is what the button reports —
70
+ * `staleTime: 0` because the draft is exactly the thing that just moved.
71
+ */
72
+ export declare function useValidateDraft(id: number | undefined, options?: {
73
+ readonly enabled?: boolean;
74
+ }): UseQueryResult<ValidationBatchResult, StapelApiError>;
75
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,qBAAqB,EACtB,MAAM,iBAAiB,CAAC;AAIzB;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,aAAa,EAAE,cAAc,CAAC,CAQ/C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAQnD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,MAAM,CAAC,EAAE,iBAAiB,EAC1B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,qBAAqB,EAAE,cAAc,CAAC,CASvD;AAED,mDAAmD;AACnD,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,cAAc,CAAC,UAAU,EAAE,cAAc,CAAC,CAS7C;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAC5B,MAAM,CAAC,EAAE,iBAAiB,EAC1B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,qBAAqB,EAAE,cAAc,CAAC,CAUvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,qBAAqB,EAAE,cAAc,CAAC,CAUvD"}
@@ -0,0 +1,117 @@
1
+ import { keepPreviousData, useQuery } from "@tanstack/react-query";
2
+ import { useActiveSessionReady } from "@stapel/core";
3
+ import { useListingsApi } from "./context.js";
4
+ import { listingsQueryKeys, pageKey } from "./queryKeys.js";
5
+ /**
6
+ * Read hooks over the listings API (frontend-standard §2). Keys are
7
+ * namespaced (see `listingsQueryKeys`).
8
+ *
9
+ * ── Two gating regimes in one module, and the line between them ────────────
10
+ *
11
+ * `useListing` / `useListingStatus` / `useListingCards` call
12
+ * `IsAuthenticatedOrReadOnly` and `AllowAny` endpoints: a visitor reads them
13
+ * with no session at all, so gating on `useActiveSessionReady` would make a
14
+ * shop window wait for a login bootstrap it has no stake in. Core's own doc
15
+ * comment carves out exactly this case.
16
+ *
17
+ * `useMyCounters` / `useMyFavorites` / `useValidateDraft` are
18
+ * `IsAuthenticated` and DO gate: firing them before the session substrate has
19
+ * settled produces a 401 that means "we asked too early", which is
20
+ * indistinguishable on screen from "you are signed out".
21
+ */
22
+ /**
23
+ * One listing in full.
24
+ *
25
+ * `retry: false` on purpose. The failures here are verdicts about the
26
+ * request: a 404 for a listing that is gone, a 403 for one that is not
27
+ * yours. Retrying a 404 three times only delays the moment the page can say
28
+ * which it was — and for a soft-deleted listing, `useListingStatus` is the
29
+ * read that can actually explain it (see `headless/ListingDetail.tsx`).
30
+ */
31
+ export function useListing(id, options) {
32
+ const api = useListingsApi();
33
+ return useQuery({
34
+ queryKey: listingsQueryKeys.detail(id ?? -1),
35
+ queryFn: ({ signal }) => api.retrieve(id, { signal }),
36
+ enabled: (options?.enabled ?? true) && id !== undefined,
37
+ retry: false,
38
+ });
39
+ }
40
+ /**
41
+ * The status probe — both axes plus `is_deleted`, for ANY listing id.
42
+ *
43
+ * The one read that survives a soft delete (`Listing.all_objects`), which is
44
+ * why a detail page runs it beside the detail rather than only after a
45
+ * failure: "this listing was removed" is a sentence, and a bare 404 is not.
46
+ */
47
+ export function useListingStatus(id, options) {
48
+ const api = useListingsApi();
49
+ return useQuery({
50
+ queryKey: listingsQueryKeys.status(id ?? -1),
51
+ queryFn: ({ signal }) => api.status(id, { signal }),
52
+ enabled: (options?.enabled ?? true) && id !== undefined,
53
+ retry: false,
54
+ });
55
+ }
56
+ /**
57
+ * A keyset page of PUBLISHED cards.
58
+ *
59
+ * `keepPreviousData`: paging a grid that blanks between pages reads as a
60
+ * failure, and the previous page is still a true answer about the previous
61
+ * cursor. The FIRST load has nothing to keep and is `loading`; a failure is
62
+ * still `failed` rather than stale rows pretending to be current.
63
+ */
64
+ export function useListingCards(params, options) {
65
+ const api = useListingsApi();
66
+ return useQuery({
67
+ queryKey: listingsQueryKeys.list(pageKey(params)),
68
+ queryFn: ({ signal }) => api.list(params, { signal }),
69
+ enabled: options?.enabled ?? true,
70
+ placeholderData: keepPreviousData,
71
+ retry: false,
72
+ });
73
+ }
74
+ /** The dashboard's three counts. Session-gated. */
75
+ export function useMyCounters(options) {
76
+ const api = useListingsApi();
77
+ const sessionReady = useActiveSessionReady();
78
+ return useQuery({
79
+ queryKey: listingsQueryKeys.myCounters(),
80
+ queryFn: ({ signal }) => api.myCounters({ signal }),
81
+ enabled: (options?.enabled ?? true) && sessionReady,
82
+ retry: false,
83
+ });
84
+ }
85
+ /** A keyset page of the caller's favourites. Session-gated. */
86
+ export function useMyFavorites(params, options) {
87
+ const api = useListingsApi();
88
+ const sessionReady = useActiveSessionReady();
89
+ return useQuery({
90
+ queryKey: listingsQueryKeys.myFavorites(pageKey(params)),
91
+ queryFn: ({ signal }) => api.myFavorites(params, { signal }),
92
+ enabled: (options?.enabled ?? true) && sessionReady,
93
+ placeholderData: keepPreviousData,
94
+ retry: false,
95
+ });
96
+ }
97
+ /**
98
+ * What publishing WOULD say, without publishing.
99
+ *
100
+ * Disabled by default: it is a round trip per call and the composer's own
101
+ * mirror answers the same question for free most of the time. A composer
102
+ * turns it on when the person asks ("check my listing") and after a save, so
103
+ * the SERVER's opinion of the saved draft is what the button reports —
104
+ * `staleTime: 0` because the draft is exactly the thing that just moved.
105
+ */
106
+ export function useValidateDraft(id, options) {
107
+ const api = useListingsApi();
108
+ const sessionReady = useActiveSessionReady();
109
+ return useQuery({
110
+ queryKey: listingsQueryKeys.validateDraft(id ?? -1),
111
+ queryFn: ({ signal }) => api.validateDraft(id, { signal }),
112
+ enabled: (options?.enabled ?? false) && id !== undefined && sessionReady,
113
+ staleTime: 0,
114
+ retry: false,
115
+ });
116
+ }
117
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Namespaced TanStack Query keys (frontend-standard §2 — namespaced keys).
3
+ * Everything under the `"listings"` root so a host can invalidate the whole
4
+ * module or match a single read. Explicit tuple return types satisfy
5
+ * `--isolatedDeclarations`.
6
+ *
7
+ * THE KEY IS THE REQUEST for the paginated reads: a page is keyed on the very
8
+ * parameter object the client is handed, normalized so that "no cursor" and
9
+ * "an explicitly undefined cursor" cannot cache twice. Two states that
10
+ * produce the same request therefore share a cache entry, and one that
11
+ * produces a different request cannot silently reuse a page — the stale-page
12
+ * bug (new filter, cached rows) is unwritable rather than merely avoided.
13
+ */
14
+ import type { ListingPageParams } from "../api/types.js";
15
+ /** The normalized page cursor a list read is keyed on. */
16
+ export interface ListingPageKey {
17
+ readonly anchor: string | null;
18
+ readonly direction: string | null;
19
+ readonly limit: number | null;
20
+ }
21
+ /** Normalize a page's parameters into its key. Exported because a test that
22
+ * asserts "these two states hit one cache entry" must build the key the same
23
+ * way the hook does. */
24
+ export declare function pageKey(params?: ListingPageParams): ListingPageKey;
25
+ export declare const listingsQueryKeys: {
26
+ readonly all: readonly ["listings"];
27
+ /** The published card list. */
28
+ list(page: ListingPageKey): readonly ["listings", "list", ListingPageKey];
29
+ /** One listing in full. */
30
+ detail(id: number): readonly ["listings", "detail", number];
31
+ /** The AllowAny status probe — a SEPARATE entry from the detail on purpose:
32
+ * it answers for a soft-deleted listing the detail 404s on, so caching them
33
+ * together would let a 404 evict the one read that can explain it. */
34
+ status(id: number): readonly ["listings", "status", number];
35
+ /** The dashboard's three counts. */
36
+ myCounters(): readonly ["listings", "my", "counters"];
37
+ /** The caller's favourites, one keyset page. */
38
+ myFavorites(page: ListingPageKey): readonly ["listings", "my", "favorites", ListingPageKey];
39
+ /** The owner's own rows, per tab — see `headless/MyListings.tsx` for why
40
+ * this read goes through an injected source. */
41
+ mine(tab: string, page: ListingPageKey): readonly ["listings", "my", "listings", string, ListingPageKey];
42
+ /** `GET /{pk}/validate-draft/` — the dry run of a publish. */
43
+ validateDraft(id: number): readonly ["listings", "validate-draft", number];
44
+ /** Every favourites PAGE, for an invalidation after a toggle — the cursor
45
+ * is not known at the write, so the prefix is what a write can target. */
46
+ allFavorites(): readonly ["listings", "my", "favorites"];
47
+ /** Every published-card PAGE, same reason. */
48
+ allLists(): readonly ["listings", "list"];
49
+ };
50
+ //# sourceMappingURL=queryKeys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryKeys.d.ts","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;wBAEwB;AACxB,wBAAgB,OAAO,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAMlE;AAID,eAAO,MAAM,iBAAiB,EAAE;IAC9B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACpC,+BAA+B;IAC/B,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1E,2BAA2B;IAC3B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D;;0EAEsE;IACtE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D,oCAAoC;IACpC,UAAU,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IACtD,gDAAgD;IAChD,WAAW,CACT,IAAI,EAAE,cAAc,GACnB,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAC5D;oDACgD;IAChD,IAAI,CACF,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,cAAc,GACnB,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IACnE,8DAA8D;IAC9D,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC3E;8EAC0E;IAC1E,YAAY,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IACzD,8CAA8C;IAC9C,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;CAY3C,CAAC"}
@@ -0,0 +1,24 @@
1
+ /** Normalize a page's parameters into its key. Exported because a test that
2
+ * asserts "these two states hit one cache entry" must build the key the same
3
+ * way the hook does. */
4
+ export function pageKey(params) {
5
+ return {
6
+ anchor: params?.anchor ?? null,
7
+ direction: params?.direction ?? null,
8
+ limit: params?.limit ?? null,
9
+ };
10
+ }
11
+ const ROOT = "listings";
12
+ export const listingsQueryKeys = {
13
+ all: [ROOT],
14
+ list: (page) => [ROOT, "list", page],
15
+ detail: (id) => [ROOT, "detail", id],
16
+ status: (id) => [ROOT, "status", id],
17
+ myCounters: () => [ROOT, "my", "counters"],
18
+ myFavorites: (page) => [ROOT, "my", "favorites", page],
19
+ mine: (tab, page) => [ROOT, "my", "listings", tab, page],
20
+ validateDraft: (id) => [ROOT, "validate-draft", id],
21
+ allFavorites: () => [ROOT, "my", "favorites"],
22
+ allLists: () => [ROOT, "list"],
23
+ };
24
+ //# sourceMappingURL=queryKeys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAsBA;;wBAEwB;AACxB,MAAM,UAAU,OAAO,CAAC,MAA0B;IAChD,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI;QAC9B,SAAS,EAAE,MAAM,EAAE,SAAS,IAAI,IAAI;QACpC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,IAAI,GAAG,UAAmB,CAAC;AAEjC,MAAM,CAAC,MAAM,iBAAiB,GA6B1B;IACF,GAAG,EAAE,CAAC,IAAI,CAAC;IACX,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC;IACpC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC;IACtD,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC;IACxD,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC;IACnD,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC;IAC7C,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;CAC/B,CAAC"}
@@ -0,0 +1,69 @@
1
+ import type { CreateModuleRuntimeOptions, ModuleRuntime } from "@stapel/core";
2
+ import type { StapelImage } from "@stapel/image";
3
+ import type { ListingsApi } from "../api/listingsApi.js";
4
+ import type { ListingDraftLimits } from "./validation.js";
5
+ /**
6
+ * Turn a stored image reference into something renderable.
7
+ *
8
+ * ── Why this is a seam and not a URL builder ───────────────────────────────
9
+ *
10
+ * `Listing.images` is a list of OPAQUE CDN references (`<type>/<hash>`), and
11
+ * the listings contract offers no way to resolve one: there is no
12
+ * `GET /listings/{pk}/images/`, and stapel-cdn's `file/exists/` is
13
+ * owner-scoped (`uploaded_by=request.user`), so `@stapel/cdn-react`'s
14
+ * `useCdnRef` can resolve a person's OWN draft and can never render a
15
+ * stranger's gallery — it says so itself. There is therefore no public
16
+ * read-by-reference anywhere in this fleet's contracts today.
17
+ *
18
+ * The honest answer is a seam, not an invented URL convention: a deployment
19
+ * knows where its CDN serves from, and hands that knowledge in once. A pair
20
+ * that guessed `${cdnBase}/${ref}` would be writing a contract nobody agreed
21
+ * to, and it would break the first host that puts a signature on the path.
22
+ *
23
+ * Returning a full `StapelImage` rather than a string is what buys the
24
+ * variant ladder: with `variants` populated, `@stapel/image`'s `<Image>`
25
+ * measures the slot and picks a tier; with only `url`, it shows that one.
26
+ * Both are supported, and a resolver that has nothing for a reference returns
27
+ * `undefined` — the skin then says "photo unavailable" instead of drawing a
28
+ * broken `<img>`. Recorded as an upstream gap in MODULE.md.
29
+ */
30
+ export type ListingImageResolver = (ref: string) => StapelImage | undefined;
31
+ export interface CreateListingsRuntimeOptions extends CreateModuleRuntimeOptions {
32
+ /** See {@link ListingImageResolver}. Absent = no gallery is drawn, and the
33
+ * skin names the reason. */
34
+ readonly resolveImage?: ListingImageResolver;
35
+ /**
36
+ * The deployment's own ceilings, mirrored client-side. Partial: only the
37
+ * ones that differ from `stapel_listings/conf.py`'s library defaults.
38
+ */
39
+ readonly limits?: Partial<ListingDraftLimits>;
40
+ /** Currency a new draft starts in. Owner verdict F6: RUB. */
41
+ readonly currency?: string;
42
+ }
43
+ /**
44
+ * The wired listings runtime — core's `ModuleRuntime` bound to this pair's
45
+ * API (slim wave §21/S2), plus the three pieces of deployment knowledge the
46
+ * pair cannot derive: how to render a reference, where the ceilings are, and
47
+ * what currency the shop trades in.
48
+ *
49
+ * ```tsx
50
+ * const runtime = createListingsRuntime({
51
+ * baseUrl: "/listings/api/v1/",
52
+ * resolveImage: (ref) => myCdn.describe(ref),
53
+ * });
54
+ * <ListingsProvider runtime={runtime}>…</ListingsProvider>
55
+ * ```
56
+ *
57
+ * NOT session-gated as a whole. The public reads (`retrieve`, `status`,
58
+ * `list`) are `IsAuthenticatedOrReadOnly` and answer a visitor who will never
59
+ * sign in; the owner reads and every write are `IsAuthenticated` and the
60
+ * hooks that call them gate on `useActiveSessionReady` individually. Gating
61
+ * the module would make a shop window wait for a login bootstrap.
62
+ */
63
+ export interface ListingsRuntime extends ModuleRuntime<ListingsApi> {
64
+ readonly resolveImage: ListingImageResolver | undefined;
65
+ readonly limits: ListingDraftLimits;
66
+ readonly currency: string;
67
+ }
68
+ export declare function createListingsRuntime(options: CreateListingsRuntimeOptions): ListingsRuntime;
69
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;AAE5E,MAAM,WAAW,4BACf,SAAQ,0BAA0B;IAClC;gCAC4B;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC;IAC7C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC9C,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa,CAAC,WAAW,CAAC;IACjE,QAAQ,CAAC,YAAY,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,eAAe,CAQjB"}
@@ -0,0 +1,14 @@
1
+ import { createModuleRuntime } from "@stapel/core";
2
+ import { createListingsApi } from "../api/listingsApi.js";
3
+ import { DEFAULT_LISTING_CURRENCY } from "../api/types.js";
4
+ import { DEFAULT_DRAFT_LIMITS } from "./validation.js";
5
+ export function createListingsRuntime(options) {
6
+ const base = createModuleRuntime((client) => createListingsApi(client), options);
7
+ return {
8
+ ...base,
9
+ resolveImage: options.resolveImage,
10
+ limits: { ...DEFAULT_DRAFT_LIMITS, ...options.limits },
11
+ currency: options.currency ?? DEFAULT_LISTING_CURRENCY,
12
+ };
13
+ }
14
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAsEvD,MAAM,UAAU,qBAAqB,CACnC,OAAqC;IAErC,MAAM,IAAI,GAAG,mBAAmB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IACjF,OAAO;QACL,GAAG,IAAI;QACP,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,MAAM,EAAE,EAAE,GAAG,oBAAoB,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE;QACtD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,wBAAwB;KACvD,CAAC;AACJ,CAAC"}