@mohasinac/appkit 4.60.1 → 4.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +19 -3
- package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
- package/dist/_internal/server/features/auctions/actions.d.ts +1 -1
- package/dist/_internal/server/features/auctions/actions.js +12 -1
- package/dist/_internal/server/features/blog/actions.d.ts +1 -1
- package/dist/_internal/server/features/blog/actions.js +12 -1
- package/dist/_internal/server/features/brands/actions.d.ts +1 -1
- package/dist/_internal/server/features/brands/actions.js +12 -1
- package/dist/_internal/server/features/cart/actions.d.ts +1 -1
- package/dist/_internal/server/features/cart/actions.js +12 -1
- package/dist/_internal/server/features/checkout/actions.js +0 -1
- package/dist/_internal/server/features/events/actions.d.ts +1 -1
- package/dist/_internal/server/features/events/actions.js +12 -1
- package/dist/_internal/server/features/gateway-events/redact.js +14 -2
- package/dist/_internal/server/features/history/actions.d.ts +1 -1
- package/dist/_internal/server/features/history/actions.js +12 -1
- package/dist/_internal/server/features/lottery/actions.d.ts +1 -1
- package/dist/_internal/server/features/lottery/actions.js +12 -1
- package/dist/_internal/server/features/orders/actions.d.ts +1 -1
- package/dist/_internal/server/features/orders/actions.js +12 -1
- package/dist/_internal/server/features/orders/cancellation-refund.js +0 -1
- package/dist/_internal/server/features/orders/service.js +31 -1
- package/dist/_internal/server/features/payouts/actions.d.ts +1 -1
- package/dist/_internal/server/features/payouts/actions.js +12 -1
- package/dist/_internal/server/features/points/award.js +9 -1
- package/dist/_internal/server/features/pre-orders/actions.d.ts +1 -1
- package/dist/_internal/server/features/pre-orders/actions.js +12 -1
- package/dist/_internal/server/features/prize-draws/actions.d.ts +1 -1
- package/dist/_internal/server/features/prize-draws/actions.js +12 -1
- package/dist/_internal/server/features/products/actions.d.ts +1 -1
- package/dist/_internal/server/features/products/actions.js +12 -1
- package/dist/_internal/server/features/promotions/actions.d.ts +1 -1
- package/dist/_internal/server/features/promotions/actions.js +12 -1
- package/dist/_internal/server/features/protection-claims/actions.d.ts +2 -2
- package/dist/_internal/server/features/protection-claims/actions.js +21 -12
- package/dist/_internal/server/features/raffle/actions.d.ts +1 -1
- package/dist/_internal/server/features/raffle/actions.js +12 -1
- package/dist/_internal/server/features/refunds/actions.d.ts +1 -3
- package/dist/_internal/server/features/refunds/actions.js +14 -2
- package/dist/_internal/server/features/reviews/actions.d.ts +1 -1
- package/dist/_internal/server/features/reviews/actions.js +12 -1
- package/dist/_internal/server/features/wishlist/actions.d.ts +1 -1
- package/dist/_internal/server/features/wishlist/actions.js +12 -1
- package/dist/_internal/server/jobs/core/obligationSweep.js +0 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +93 -76
- package/dist/_internal/server/jobs/core/testDataCleanup.d.ts +1 -1
- package/dist/_internal/server/jobs/core/testDataCleanup.js +22 -1
- package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +16 -4
- package/dist/_internal/shared/actions/action-registry.js +149 -9
- package/dist/_internal/shared/features/promotions/schema.d.ts +66 -0
- package/dist/_internal/shared/features/promotions/schema.js +7 -0
- package/dist/_internal/shared/media/limits.d.ts +10 -0
- package/dist/_internal/shared/media/limits.js +10 -0
- package/dist/_internal/shared/obligations/deadlines.js +11 -1
- package/dist/_internal/shared/tester/window.d.ts +25 -0
- package/dist/_internal/shared/tester/window.js +66 -0
- package/dist/_internal/shared/types/action-result.js +15 -1
- package/dist/client/api/surface-error.js +16 -2
- package/dist/client/api/useApiMutation.js +17 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/constants/field-names.d.ts +17 -0
- package/dist/constants/field-names.js +23 -0
- package/dist/core/server-action.js +19 -3
- package/dist/core/unit-of-work.js +15 -1
- package/dist/errors/error-mapping.js +26 -1
- package/dist/errors/firestore-codes.d.ts +37 -0
- package/dist/errors/firestore-codes.js +52 -0
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +12 -2
- package/dist/features/account/components/UserOffersPanel.js +5 -3
- package/dist/features/admin/components/AdminActionIndexView.js +100 -5
- package/dist/features/admin/components/AdminAddressClustersView.js +21 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +8 -8
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselView.js +96 -4
- package/dist/features/admin/components/AdminCouponEditorView.js +8 -0
- package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
- package/dist/features/admin/components/AdminGatewayEventsView.js +2 -2
- package/dist/features/admin/components/AdminMediaView.js +36 -3
- package/dist/features/admin/components/AdminNavigationView.js +4 -1
- package/dist/features/admin/components/AdminOffersView.js +2 -1
- package/dist/features/admin/components/AdminPaymentClustersView.js +19 -2
- package/dist/features/admin/components/AdminProductEditorView.js +2 -2
- package/dist/features/admin/components/AdminSectionsView.d.ts +10 -0
- package/dist/features/admin/components/AdminSectionsView.js +486 -224
- package/dist/features/admin/components/DataListingView.js +2 -0
- package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +4 -1
- package/dist/features/admin/hooks/useOptimisticRowFlag.d.ts +33 -0
- package/dist/features/admin/hooks/useOptimisticRowFlag.js +87 -0
- package/dist/features/admin/repository/notification.repository.d.ts +15 -0
- package/dist/features/admin/repository/notification.repository.js +32 -0
- package/dist/features/admin/repository/site-settings.repository.d.ts +2 -1
- package/dist/features/admin/repository/site-settings.repository.js +7 -0
- package/dist/features/admin/schemas/action-index-form.d.ts +34 -0
- package/dist/features/admin/schemas/action-index-form.js +115 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +1 -1
- package/dist/features/auth/components/RegisterForm.js +2 -1
- package/dist/features/catalogue/actions/approve-catalogue-listing.d.ts +1 -1
- package/dist/features/catalogue/actions/approve-catalogue-listing.js +12 -1
- package/dist/features/catalogue/actions/list-from-catalogue.d.ts +1 -1
- package/dist/features/catalogue/actions/list-from-catalogue.js +12 -1
- package/dist/features/catalogue/actions/submit-for-approval.d.ts +1 -1
- package/dist/features/catalogue/actions/submit-for-approval.js +12 -1
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
- package/dist/features/categories/repository/price-observation.repository.d.ts +19 -0
- package/dist/features/categories/repository/price-observation.repository.js +21 -2
- package/dist/features/events/actions/event-actions.js +4 -1
- package/dist/features/events/components/AdminEventEditorView.js +79 -50
- package/dist/features/events/components/EventsListPageView.js +2 -1
- package/dist/features/events/schemas/firestore.d.ts +17 -0
- package/dist/features/events/schemas/firestore.js +1 -0
- package/dist/features/events/utils/sort-pinned.d.ts +33 -0
- package/dist/features/events/utils/sort-pinned.js +46 -0
- package/dist/features/gateway-events/repository/gateway-events.repository.js +3 -40
- package/dist/features/gateway-events/schemas/index.d.ts +2 -2
- package/dist/features/inventory/components/AdminStorageBoxesView.js +55 -39
- package/dist/features/inventory/components/ReceiveStationView.js +61 -17
- package/dist/features/inventory/repository/storage-boxes.repository.d.ts +3 -1
- package/dist/features/inventory/repository/storage-boxes.repository.js +36 -3
- package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
- package/dist/features/obligations/repository/obligations.repository.js +10 -1
- package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
- package/dist/features/orders/components/RefundRequestView.js +4 -4
- package/dist/features/orders/schemas/firestore.d.ts +19 -0
- package/dist/features/orders/schemas/firestore.js +19 -0
- package/dist/features/orders/schemas/index.d.ts +6 -6
- package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +1 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.js +3 -14
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/products/components/GroupMemberPicker.js +4 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +1 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/ProductDetailActions.js +2 -1
- package/dist/features/products/components/ProductForm.js +2 -2
- package/dist/features/products/components/ProductGrid.js +1 -1
- package/dist/features/promotions/repository/claimed-coupons.repository.js +31 -1
- package/dist/features/promotions/schemas/firestore.d.ts +22 -0
- package/dist/features/promotions/schemas/firestore.js +22 -0
- package/dist/features/promotions/schemas/index.d.ts +479 -6
- package/dist/features/promotions/schemas/index.js +153 -0
- package/dist/features/protection-claims/repository/protection-claims.repository.js +22 -15
- package/dist/features/seller/components/FulfillmentView.js +3 -2
- package/dist/features/seller/components/SellerAddressesView.js +32 -4
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +62 -3
- package/dist/features/seller/components/SellerBundlesView.js +53 -14
- package/dist/features/seller/components/SellerGroupedListingsView.js +23 -3
- package/dist/features/seller/components/SellerListingTypeView.js +56 -10
- package/dist/features/seller/components/SellerPayoutMethodsView.js +33 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +48 -9
- package/dist/features/seller/components/SellerProductsView.js +5 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +33 -1
- package/dist/features/seller/components/SellerShippingView.js +2 -1
- package/dist/features/seller/components/SellerStoreCategoriesView.js +33 -1
- package/dist/features/seller/components/SellerSupportView.js +69 -2
- package/dist/features/seller/schemas/coupon-form.d.ts +5 -0
- package/dist/features/seller/schemas/coupon-form.js +18 -0
- package/dist/features/shell/SectionForm.js +35 -9
- package/dist/features/shell/build-sections.js +14 -1
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +54 -1
- package/dist/features/shipments/components/AdminShipmentProjectionsView.js +3 -2
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerPrintPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerRetryPanel.js +24 -4
- package/dist/features/sticker-trace/components/StickerTraceStudio.js +2 -1
- package/dist/features/sticker-trace/server.d.ts +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
- package/dist/http/api-handler.js +4 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +6 -4
- package/dist/next/routing/route-map.d.ts +4 -2
- package/dist/next/routing/route-map.js +19 -1
- package/dist/schemas/registry.d.ts +15 -10
- package/dist/schemas/sieve.d.ts +3 -3
- package/dist/server.d.ts +1 -2
- package/dist/server.js +6 -4
- package/dist/styles.css +51 -0
- package/dist/ui/components/Checkbox.js +10 -2
- package/dist/ui/components/DateInput.js +8 -1
- package/dist/ui/components/Input.js +22 -2
- package/dist/ui/components/PaginatedSelect.d.ts +8 -19
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/Radio.js +8 -1
- package/dist/ui/components/Select.js +10 -2
- package/dist/ui/components/TagInput.d.ts +8 -1
- package/dist/ui/components/TagInput.js +4 -3
- package/dist/ui/components/Textarea.js +10 -2
- package/dist/ui/forms/FieldCheckbox.js +1 -1
- package/dist/ui/forms/FormErrorList.d.ts +0 -16
- package/dist/ui/forms/FormErrorList.js +34 -1
- package/dist/ui/forms/apply-zod-issues.d.ts +19 -1
- package/dist/ui/forms/apply-zod-issues.js +23 -1
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +1822 -1812
- package/dist/features/tester/utils/phases.d.ts +0 -48
- package/dist/features/tester/utils/phases.js +0 -139
|
@@ -36,6 +36,25 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
36
36
|
minPurchase?: number | undefined;
|
|
37
37
|
maxDiscount?: number | undefined;
|
|
38
38
|
}>;
|
|
39
|
+
bxgy: z.ZodOptional<z.ZodObject<{
|
|
40
|
+
buyQuantity: z.ZodNumber;
|
|
41
|
+
getQuantity: z.ZodNumber;
|
|
42
|
+
maxItemPrice: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodNumber, number, number>, number, number>>;
|
|
43
|
+
applicableProducts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
44
|
+
applicableCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
45
|
+
}, "strip", z.ZodTypeAny, {
|
|
46
|
+
buyQuantity: number;
|
|
47
|
+
getQuantity: number;
|
|
48
|
+
maxItemPrice?: number | undefined;
|
|
49
|
+
applicableProducts?: string[] | undefined;
|
|
50
|
+
applicableCategories?: string[] | undefined;
|
|
51
|
+
}, {
|
|
52
|
+
buyQuantity: number;
|
|
53
|
+
getQuantity: number;
|
|
54
|
+
maxItemPrice?: number | undefined;
|
|
55
|
+
applicableProducts?: string[] | undefined;
|
|
56
|
+
applicableCategories?: string[] | undefined;
|
|
57
|
+
}>>;
|
|
39
58
|
usage: z.ZodObject<{
|
|
40
59
|
totalLimit: z.ZodOptional<z.ZodNumber>;
|
|
41
60
|
perUserLimit: z.ZodDefault<z.ZodNumber>;
|
|
@@ -91,6 +110,13 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
91
110
|
endDate: string;
|
|
92
111
|
startDate: string;
|
|
93
112
|
};
|
|
113
|
+
bxgy?: {
|
|
114
|
+
buyQuantity: number;
|
|
115
|
+
getQuantity: number;
|
|
116
|
+
maxItemPrice?: number | undefined;
|
|
117
|
+
applicableProducts?: string[] | undefined;
|
|
118
|
+
applicableCategories?: string[] | undefined;
|
|
119
|
+
} | undefined;
|
|
94
120
|
restrictions?: {
|
|
95
121
|
firstTimeUserOnly: boolean;
|
|
96
122
|
applicableProducts?: string[] | undefined;
|
|
@@ -115,6 +141,13 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
115
141
|
startDate: string;
|
|
116
142
|
isActive?: boolean | undefined;
|
|
117
143
|
};
|
|
144
|
+
bxgy?: {
|
|
145
|
+
buyQuantity: number;
|
|
146
|
+
getQuantity: number;
|
|
147
|
+
maxItemPrice?: number | undefined;
|
|
148
|
+
applicableProducts?: string[] | undefined;
|
|
149
|
+
applicableCategories?: string[] | undefined;
|
|
150
|
+
} | undefined;
|
|
118
151
|
restrictions?: {
|
|
119
152
|
firstTimeUserOnly?: boolean | undefined;
|
|
120
153
|
applicableProducts?: string[] | undefined;
|
|
@@ -139,6 +172,25 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
139
172
|
minPurchase?: number | undefined;
|
|
140
173
|
maxDiscount?: number | undefined;
|
|
141
174
|
}>>;
|
|
175
|
+
bxgy: z.ZodOptional<z.ZodOptional<z.ZodObject<{
|
|
176
|
+
buyQuantity: z.ZodNumber;
|
|
177
|
+
getQuantity: z.ZodNumber;
|
|
178
|
+
maxItemPrice: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodNumber, number, number>, number, number>>;
|
|
179
|
+
applicableProducts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
180
|
+
applicableCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
181
|
+
}, "strip", z.ZodTypeAny, {
|
|
182
|
+
buyQuantity: number;
|
|
183
|
+
getQuantity: number;
|
|
184
|
+
maxItemPrice?: number | undefined;
|
|
185
|
+
applicableProducts?: string[] | undefined;
|
|
186
|
+
applicableCategories?: string[] | undefined;
|
|
187
|
+
}, {
|
|
188
|
+
buyQuantity: number;
|
|
189
|
+
getQuantity: number;
|
|
190
|
+
maxItemPrice?: number | undefined;
|
|
191
|
+
applicableProducts?: string[] | undefined;
|
|
192
|
+
applicableCategories?: string[] | undefined;
|
|
193
|
+
}>>>;
|
|
142
194
|
usage: z.ZodOptional<z.ZodObject<{
|
|
143
195
|
totalLimit: z.ZodOptional<z.ZodNumber>;
|
|
144
196
|
perUserLimit: z.ZodDefault<z.ZodNumber>;
|
|
@@ -184,6 +236,13 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
184
236
|
minPurchase: number;
|
|
185
237
|
maxDiscount?: number | undefined;
|
|
186
238
|
} | undefined;
|
|
239
|
+
bxgy?: {
|
|
240
|
+
buyQuantity: number;
|
|
241
|
+
getQuantity: number;
|
|
242
|
+
maxItemPrice?: number | undefined;
|
|
243
|
+
applicableProducts?: string[] | undefined;
|
|
244
|
+
applicableCategories?: string[] | undefined;
|
|
245
|
+
} | undefined;
|
|
187
246
|
usage?: {
|
|
188
247
|
perUserLimit: number;
|
|
189
248
|
totalLimit?: number | undefined;
|
|
@@ -207,6 +266,13 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
207
266
|
minPurchase?: number | undefined;
|
|
208
267
|
maxDiscount?: number | undefined;
|
|
209
268
|
} | undefined;
|
|
269
|
+
bxgy?: {
|
|
270
|
+
buyQuantity: number;
|
|
271
|
+
getQuantity: number;
|
|
272
|
+
maxItemPrice?: number | undefined;
|
|
273
|
+
applicableProducts?: string[] | undefined;
|
|
274
|
+
applicableCategories?: string[] | undefined;
|
|
275
|
+
} | undefined;
|
|
210
276
|
usage?: {
|
|
211
277
|
totalLimit?: number | undefined;
|
|
212
278
|
perUserLimit?: number | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { COUPON_CODE_PATTERN } from "./config.js";
|
|
3
|
+
import { bxgyConfigSchema } from "../../../../features/promotions/schemas/index.js";
|
|
3
4
|
export const applyCouponSchema = z.object({
|
|
4
5
|
code: z.string().regex(COUPON_CODE_PATTERN, "Invalid coupon code format").toUpperCase(),
|
|
5
6
|
cartTotal: z.number().min(0),
|
|
@@ -17,6 +18,12 @@ export const createCouponSchema = z.object({
|
|
|
17
18
|
maxDiscount: z.number().min(0).optional(),
|
|
18
19
|
minPurchase: z.number().min(0).default(0),
|
|
19
20
|
}),
|
|
21
|
+
/*
|
|
22
|
+
* 🛑 Without this, `z.object()` strips the config the `buy_x_get_y` member
|
|
23
|
+
* of the enum above depends on — see the fuller note on `bxgyConfigSchema`.
|
|
24
|
+
* Imported rather than restated so all five write paths move together.
|
|
25
|
+
*/
|
|
26
|
+
bxgy: bxgyConfigSchema.optional(),
|
|
20
27
|
usage: z.object({
|
|
21
28
|
totalLimit: z.number().int().min(1).optional(),
|
|
22
29
|
perUserLimit: z.number().int().min(1).default(1),
|
|
@@ -27,6 +27,16 @@ export type MediaKind = "image" | "video" | "pdf";
|
|
|
27
27
|
export declare const PRODUCT_MAX_IMAGES = 24;
|
|
28
28
|
/** Videos per listing. Bounded by `ProductDocument.video` being singular. */
|
|
29
29
|
export declare const PRODUCT_MAX_VIDEOS = 1;
|
|
30
|
+
/**
|
|
31
|
+
* Photos a buyer may attach to one review.
|
|
32
|
+
*
|
|
33
|
+
* 🛑 Deliberately NOT `PRODUCT_MAX_IMAGES`, which is 24 and is a SELLER's
|
|
34
|
+
* ceiling for a whole listing. `audit-media-limit-literals` suggests that
|
|
35
|
+
* constant for any `images: z.array(…).max(n)`, and taking the suggestion
|
|
36
|
+
* here would have quadrupled what a review accepts — the right shape, the
|
|
37
|
+
* wrong number. A review is a few photos of one thing.
|
|
38
|
+
*/
|
|
39
|
+
export declare const REVIEW_MAX_IMAGES = 6;
|
|
30
40
|
/**
|
|
31
41
|
* Highest legal `index` in a product media filename.
|
|
32
42
|
*
|
|
@@ -41,6 +41,16 @@ export const MAX_VIDEO_BYTES = 50 * MEGABYTE;
|
|
|
41
41
|
export const PRODUCT_MAX_IMAGES = 24;
|
|
42
42
|
/** Videos per listing. Bounded by `ProductDocument.video` being singular. */
|
|
43
43
|
export const PRODUCT_MAX_VIDEOS = 1;
|
|
44
|
+
/**
|
|
45
|
+
* Photos a buyer may attach to one review.
|
|
46
|
+
*
|
|
47
|
+
* 🛑 Deliberately NOT `PRODUCT_MAX_IMAGES`, which is 24 and is a SELLER's
|
|
48
|
+
* ceiling for a whole listing. `audit-media-limit-literals` suggests that
|
|
49
|
+
* constant for any `images: z.array(…).max(n)`, and taking the suggestion
|
|
50
|
+
* here would have quadrupled what a review accepts — the right shape, the
|
|
51
|
+
* wrong number. A review is a few photos of one thing.
|
|
52
|
+
*/
|
|
53
|
+
export const REVIEW_MAX_IMAGES = 6;
|
|
44
54
|
/**
|
|
45
55
|
* Highest legal `index` in a product media filename.
|
|
46
56
|
*
|
|
@@ -33,7 +33,17 @@
|
|
|
33
33
|
import { addBusinessDays } from "../time/business-days.js";
|
|
34
34
|
import { OBLIGATION_POLICIES, durationToMinutes, } from "../../../features/obligations/constants/policies.js";
|
|
35
35
|
import { ALL_OBLIGATION_KINDS, } from "../../../features/obligations/schemas/firestore.js";
|
|
36
|
-
|
|
36
|
+
/*
|
|
37
|
+
* 🛑 From _internal/shared/tester, NOT features/tester/seed-data.
|
|
38
|
+
*
|
|
39
|
+
* appkit/package.json excludes "!dist/features/tester/seed-data" from the
|
|
40
|
+
* published tarball, so this production module importing from there left the
|
|
41
|
+
* package with a DANGLING import: it resolved locally (the dir exists in a
|
|
42
|
+
* local build) and broke the consumer Turbopack build, which installs the
|
|
43
|
+
* tarball — "Module not found: tester-window.js". Nothing in appkit could see
|
|
44
|
+
* it; only a real deploy could.
|
|
45
|
+
*/
|
|
46
|
+
import { windowMinutes } from "../tester/window.js";
|
|
37
47
|
/**
|
|
38
48
|
* How much a test fixture's deadlines are compressed.
|
|
39
49
|
*
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Reproduces the historical 1h/2h/3h auction stagger when nothing overrides it. */
|
|
2
|
+
export declare const DEFAULT_TESTER_WINDOW_MINUTES = 180;
|
|
3
|
+
/** Below this nothing is observable — a page load alone can outlast it. */
|
|
4
|
+
export declare const MIN_TESTER_WINDOW_MINUTES = 5;
|
|
5
|
+
/** The sandbox TTL is 7 days; a window longer than that outlives its own fixtures. */
|
|
6
|
+
export declare const MAX_TESTER_WINDOW_MINUTES: number;
|
|
7
|
+
/**
|
|
8
|
+
* Length of the testing window, in minutes.
|
|
9
|
+
*
|
|
10
|
+
* Read at call time rather than captured at module scope so a caller that sets the
|
|
11
|
+
* env var before importing a seed file still wins. Anything unparseable, non-finite
|
|
12
|
+
* or out of range falls back to the default rather than throwing: a malformed env
|
|
13
|
+
* var must not be able to brick `appkit-seed load`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function windowMinutes(): number;
|
|
16
|
+
/**
|
|
17
|
+
* A point `fraction` of the way through the window, measured from now.
|
|
18
|
+
*
|
|
19
|
+
* `windowOffset(0)` is now, `windowOffset(1)` is the window's end. Fractions above 1
|
|
20
|
+
* are allowed and mean "after this run" — that is how a fixture says "still open when
|
|
21
|
+
* testing finishes" without hard-coding a duration.
|
|
22
|
+
*/
|
|
23
|
+
export declare function windowOffset(fraction: number): Date;
|
|
24
|
+
/** The mirror of windowOffset, into the past — for fixtures that must already be closed. */
|
|
25
|
+
export declare function windowAgo(fraction: number): Date;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: Every time-bound tester fixture — auction end dates, coupon validity, event
|
|
3
|
+
* start/end, prize-draw reveal windows, offer expiry, lottery draw windows —
|
|
4
|
+
* must open and close INSIDE one testing session. Hand-written offsets like
|
|
5
|
+
* `Date.now() + 2 * 60 * 60 * 1000` cannot do that: they bake in a duration
|
|
6
|
+
* nobody can shorten, so a case reading "watch the auction actually end" is
|
|
7
|
+
* untestable without sitting there for two hours.
|
|
8
|
+
*
|
|
9
|
+
* This file is the one knob. Fixtures express WHEN as a fraction of the run
|
|
10
|
+
* window; the window's real length comes from TESTER_WINDOW_MINUTES.
|
|
11
|
+
*
|
|
12
|
+
* WHAT: windowMinutes() / windowOffset(fraction) / windowAgo(fraction).
|
|
13
|
+
*
|
|
14
|
+
* 🛑 The default is 180 minutes ON PURPOSE — it reproduces the pre-existing 1h/2h/3h
|
|
15
|
+
* auction stagger exactly (fractions 1/3, 2/3, 1), so an ordinary `appkit-seed load`
|
|
16
|
+
* behaves as it always has. Only the automated runner shortens it. Changing this
|
|
17
|
+
* default silently re-times every fixture for human testers too.
|
|
18
|
+
*
|
|
19
|
+
* @tag domain:tester
|
|
20
|
+
* @tag layer:seed
|
|
21
|
+
* @tag pattern:none
|
|
22
|
+
* @tag access:server-only
|
|
23
|
+
* @tag consumers:seed-data/*-tester-seed-data.ts
|
|
24
|
+
* @tag sideEffects:none
|
|
25
|
+
*/
|
|
26
|
+
/** Reproduces the historical 1h/2h/3h auction stagger when nothing overrides it. */
|
|
27
|
+
export const DEFAULT_TESTER_WINDOW_MINUTES = 180;
|
|
28
|
+
/** Below this nothing is observable — a page load alone can outlast it. */
|
|
29
|
+
export const MIN_TESTER_WINDOW_MINUTES = 5;
|
|
30
|
+
/** The sandbox TTL is 7 days; a window longer than that outlives its own fixtures. */
|
|
31
|
+
export const MAX_TESTER_WINDOW_MINUTES = 7 * 24 * 60;
|
|
32
|
+
/**
|
|
33
|
+
* Length of the testing window, in minutes.
|
|
34
|
+
*
|
|
35
|
+
* Read at call time rather than captured at module scope so a caller that sets the
|
|
36
|
+
* env var before importing a seed file still wins. Anything unparseable, non-finite
|
|
37
|
+
* or out of range falls back to the default rather than throwing: a malformed env
|
|
38
|
+
* var must not be able to brick `appkit-seed load`.
|
|
39
|
+
*/
|
|
40
|
+
export function windowMinutes() {
|
|
41
|
+
const raw = process.env.TESTER_WINDOW_MINUTES;
|
|
42
|
+
if (!raw)
|
|
43
|
+
return DEFAULT_TESTER_WINDOW_MINUTES;
|
|
44
|
+
const parsed = Number(raw);
|
|
45
|
+
if (!Number.isFinite(parsed))
|
|
46
|
+
return DEFAULT_TESTER_WINDOW_MINUTES;
|
|
47
|
+
if (parsed < MIN_TESTER_WINDOW_MINUTES)
|
|
48
|
+
return MIN_TESTER_WINDOW_MINUTES;
|
|
49
|
+
if (parsed > MAX_TESTER_WINDOW_MINUTES)
|
|
50
|
+
return MAX_TESTER_WINDOW_MINUTES;
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A point `fraction` of the way through the window, measured from now.
|
|
55
|
+
*
|
|
56
|
+
* `windowOffset(0)` is now, `windowOffset(1)` is the window's end. Fractions above 1
|
|
57
|
+
* are allowed and mean "after this run" — that is how a fixture says "still open when
|
|
58
|
+
* testing finishes" without hard-coding a duration.
|
|
59
|
+
*/
|
|
60
|
+
export function windowOffset(fraction) {
|
|
61
|
+
return new Date(Date.now() + fraction * windowMinutes() * 60000);
|
|
62
|
+
}
|
|
63
|
+
/** The mirror of windowOffset, into the past — for fixtures that must already be closed. */
|
|
64
|
+
export function windowAgo(fraction) {
|
|
65
|
+
return windowOffset(-fraction);
|
|
66
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
2
2
|
import { mapToHttpError } from "../../../errors/error-mapping.js";
|
|
3
|
+
import { toApiIssues } from "../../../errors/error-envelope.js";
|
|
3
4
|
import { describeCauseChain } from "../../../errors/describe-cause.js";
|
|
4
5
|
import { reportActionError } from "./action-error-reporter.js";
|
|
5
6
|
/**
|
|
@@ -57,7 +58,20 @@ export async function wrapAction(fn) {
|
|
|
57
58
|
ok: false,
|
|
58
59
|
code: mapped.code,
|
|
59
60
|
error: mapped.message,
|
|
60
|
-
|
|
61
|
+
/*
|
|
62
|
+
* 🛑 `toApiIssues`, not the raw issues — this was the ONE producer in
|
|
63
|
+
* the codebase that skipped the scrubber, and it is the one with 221
|
|
64
|
+
* call sites.
|
|
65
|
+
*
|
|
66
|
+
* A raw `ZodIssue` carries `received`: the value the user actually
|
|
67
|
+
* submitted. On a failed signup, address save or payout-details save
|
|
68
|
+
* that is an email, a phone number or a bank account, echoed straight
|
|
69
|
+
* back into the client payload. `toApiIssues` narrows each issue to
|
|
70
|
+
* `{ message, path?, code? }`, which is exactly why it exists — every
|
|
71
|
+
* other producer (`routeHandler`, `createApiHandler`, the offer
|
|
72
|
+
* actions) already routes through it.
|
|
73
|
+
*/
|
|
74
|
+
...(mapped.issues ? { issues: toApiIssues(mapped.issues) } : {}),
|
|
61
75
|
};
|
|
62
76
|
}
|
|
63
77
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isApiError } from "./ApiError.js";
|
|
2
2
|
import { ApiClientError } from "../../http/ApiClient.js";
|
|
3
|
-
import { applyZodIssues, hasAttachableIssue } from "../../ui/forms/apply-zod-issues.js";
|
|
3
|
+
import { applyZodIssues, hasAttachableIssue, rootLevelMessages } from "../../ui/forms/apply-zod-issues.js";
|
|
4
4
|
import { normalizeError } from "../../errors/normalize.js";
|
|
5
5
|
import { getErrorDisplay, toUserMessage } from "../../errors/error-display-map.js";
|
|
6
6
|
import { HTTP_ERROR_CODES } from "../../errors/error-mapping.js";
|
|
@@ -36,7 +36,21 @@ export function surfaceError(err, opts) {
|
|
|
36
36
|
const issues = issuesOf(err);
|
|
37
37
|
if (setFieldError && hasAttachableIssue(issues)) {
|
|
38
38
|
applyZodIssues(issues, (name, message) => setFieldError(name, message ?? ""));
|
|
39
|
-
|
|
39
|
+
/*
|
|
40
|
+
* 🛑 Root-level issues are NOT covered by the line above and must still be
|
|
41
|
+
* surfaced, or a cross-field rule fails silently.
|
|
42
|
+
*
|
|
43
|
+
* `applyZodIssues` skips any issue with an empty `path` — there is no
|
|
44
|
+
* field to put it on. For a mixed response (one field error plus one
|
|
45
|
+
* cross-field `.refine()`, the shape of "passwords must match" or "end
|
|
46
|
+
* date must be after start date") `hasAttachableIssue` is true, so we used
|
|
47
|
+
* to take this branch and `return` with the refine message discarded: the
|
|
48
|
+
* save was refused and nothing on screen said why.
|
|
49
|
+
*/
|
|
50
|
+
for (const message of rootLevelMessages(issues)) {
|
|
51
|
+
showToast(message, "error");
|
|
52
|
+
}
|
|
53
|
+
return; // field errors inline; only the unattachable ones toast
|
|
40
54
|
}
|
|
41
55
|
if (hasStableErrorCode(err)) {
|
|
42
56
|
const display = getErrorDisplay(err.code);
|
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { useMutation, } from "@tanstack/react-query";
|
|
4
4
|
import { useToast } from "../../ui/components/Toast.js";
|
|
5
5
|
import { surfaceError } from "./surface-error.js";
|
|
6
|
+
import { toUserMessage } from "../../errors/error-display-map.js";
|
|
6
7
|
export function useApiMutation(options) {
|
|
7
8
|
const { showToast, updateToast } = useToast();
|
|
8
9
|
const loadingIdRef = React.useRef(null);
|
|
@@ -40,7 +41,22 @@ export function useApiMutation(options) {
|
|
|
40
41
|
onSettled: (data, err, variables, context, ctx) => {
|
|
41
42
|
if (loadingIdRef.current) {
|
|
42
43
|
if (err) {
|
|
43
|
-
|
|
44
|
+
/*
|
|
45
|
+
* 🛑 This was `err instanceof Error ? err.message : "Action failed"`
|
|
46
|
+
* — verbatim the inverted ternary `audit-raw-error-text`'s own
|
|
47
|
+
* header calls "the dominant shape", sitting two lines below the
|
|
48
|
+
* correct `surfaceError` call in the same hook. The authored
|
|
49
|
+
* sentence showed only when the value was NOT an Error, i.e. almost
|
|
50
|
+
* never, so the normal case published the raw message.
|
|
51
|
+
*
|
|
52
|
+
* It was dormant only because `loadingMessage` currently has no
|
|
53
|
+
* call sites; the first consumer to set one would have got two
|
|
54
|
+
* toasts, one of them raw. `updateToast` is also absent from that
|
|
55
|
+
* audit's SINKS list, so nothing would have caught it.
|
|
56
|
+
*/
|
|
57
|
+
updateToast(loadingIdRef.current, "error", toUserMessage(err?.code, undefined, {
|
|
58
|
+
fallback: "Action failed",
|
|
59
|
+
}));
|
|
44
60
|
}
|
|
45
61
|
else if (!successMessage) {
|
|
46
62
|
updateToast(loadingIdRef.current, "success", "Done");
|
package/dist/client.d.ts
CHANGED
|
@@ -455,7 +455,7 @@ export type { OwnerQuickActionsProps, OwnerQuickActionsResource } from "./featur
|
|
|
455
455
|
export type { StoreListingEntitlements } from "./features/seller/hooks/useStoreListingEntitlements";
|
|
456
456
|
export type { ListingTypePlugin } from "./_internal/shared/listing-types/_registry";
|
|
457
457
|
export type { ListingType } from "./features/products/types/index";
|
|
458
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
458
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, REVIEW_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
459
459
|
export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
|
|
460
460
|
export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
|
|
461
461
|
export type { ScamAwarenessModalProps } from "./features/scams/components/ScamAwarenessModal";
|
package/dist/client.js
CHANGED
|
@@ -493,7 +493,7 @@ export { YourPurchaseStrip } from "./features/orders/components/YourPurchaseStri
|
|
|
493
493
|
export { ReviewFormDrawer, reviewFormSchema } from "./features/reviews/components/ReviewFormDrawer.js";
|
|
494
494
|
export { PurchaseReviewAffordance } from "./features/reviews/components/PurchaseReviewAffordance.js";
|
|
495
495
|
// Media upload limits — shared by client uploaders + server sign/finalize routes.
|
|
496
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits.js";
|
|
496
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, REVIEW_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits.js";
|
|
497
497
|
export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal.js";
|
|
498
498
|
export { SellerTrustBadge } from "./features/scams/components/SellerTrustBadge.js";
|
|
499
499
|
// [CLIENT-ONLY] — Admin panel components for action/nav permission management.
|
|
@@ -535,6 +535,7 @@ export declare const SELLER_ENDPOINTS: {
|
|
|
535
535
|
readonly BUNDLES: "/api/store/bundles";
|
|
536
536
|
/** Tickets raised ABOUT this store (W11). */
|
|
537
537
|
readonly SUPPORT: "/api/store/support";
|
|
538
|
+
readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
|
|
538
539
|
readonly BUNDLE_BY_ID: (id: string) => string;
|
|
539
540
|
readonly ORDERS_ASSIGN: (orderId: string) => string;
|
|
540
541
|
readonly PRODUCT_BY_ID: (id: string) => string;
|
|
@@ -1061,6 +1062,7 @@ export declare const API_ENDPOINTS: {
|
|
|
1061
1062
|
readonly BUNDLES: "/api/store/bundles";
|
|
1062
1063
|
/** Tickets raised ABOUT this store (W11). */
|
|
1063
1064
|
readonly SUPPORT: "/api/store/support";
|
|
1065
|
+
readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
|
|
1064
1066
|
readonly BUNDLE_BY_ID: (id: string) => string;
|
|
1065
1067
|
readonly ORDERS_ASSIGN: (orderId: string) => string;
|
|
1066
1068
|
readonly PRODUCT_BY_ID: (id: string) => string;
|
|
@@ -1608,6 +1610,7 @@ export declare const API_ROUTES: {
|
|
|
1608
1610
|
readonly BUNDLES: "/api/store/bundles";
|
|
1609
1611
|
/** Tickets raised ABOUT this store (W11). */
|
|
1610
1612
|
readonly SUPPORT: "/api/store/support";
|
|
1613
|
+
readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
|
|
1611
1614
|
readonly BUNDLE_BY_ID: (id: string) => string;
|
|
1612
1615
|
readonly ORDERS_ASSIGN: (orderId: string) => string;
|
|
1613
1616
|
readonly PRODUCT_BY_ID: (id: string) => string;
|
|
@@ -690,6 +690,7 @@ export const SELLER_ENDPOINTS = {
|
|
|
690
690
|
BUNDLES: "/api/store/bundles",
|
|
691
691
|
/** Tickets raised ABOUT this store (W11). */
|
|
692
692
|
SUPPORT: "/api/store/support",
|
|
693
|
+
SUPPORT_TICKET_BY_ID: (id) => `/api/store/support/${id}`,
|
|
693
694
|
BUNDLE_BY_ID: (id) => `/api/store/bundles/${id}`,
|
|
694
695
|
ORDERS_ASSIGN: (orderId) => `/api/store/orders/${orderId}/assign`,
|
|
695
696
|
PRODUCT_BY_ID: (id) => `/api/store/products/${id}`,
|
|
@@ -982,6 +982,23 @@ export declare const COMMON_FIELDS: {
|
|
|
982
982
|
readonly IS_ACTIVE: "isActive";
|
|
983
983
|
readonly ORDER: "order";
|
|
984
984
|
};
|
|
985
|
+
/**
|
|
986
|
+
* The idempotency rows behind a refund.
|
|
987
|
+
*
|
|
988
|
+
* 🛑 `STATE` is the claim's own lifecycle ("claimed" → settled), NOT a
|
|
989
|
+
* postal state. `audit-route-nav-field-constants` matches a raw field
|
|
990
|
+
* string against every constant that happens to share its VALUE, so it
|
|
991
|
+
* suggested `ADDRESS_FIELDS.STATE` here — right string, wrong concept, and
|
|
992
|
+
* taking the suggestion would have made the query read as address filtering.
|
|
993
|
+
*/
|
|
994
|
+
export declare const REFUND_CLAIM_FIELDS: {
|
|
995
|
+
readonly STATE: "state";
|
|
996
|
+
readonly CLAIMED_AT: "claimedAt";
|
|
997
|
+
};
|
|
998
|
+
/** Observed market prices, keyed by the taxonomy node they were seen under. */
|
|
999
|
+
export declare const PRICE_OBSERVATION_FIELDS: {
|
|
1000
|
+
readonly CATEGORY_ID: "categoryId";
|
|
1001
|
+
};
|
|
985
1002
|
export declare const OAUTH_STATE_VALUES: {
|
|
986
1003
|
readonly PENDING: "pending";
|
|
987
1004
|
readonly USED: "used";
|
|
@@ -1175,6 +1175,29 @@ export const COMMON_FIELDS = {
|
|
|
1175
1175
|
ORDER: "order",
|
|
1176
1176
|
};
|
|
1177
1177
|
// ============================================================================
|
|
1178
|
+
// REFUND CLAIMS
|
|
1179
|
+
// ============================================================================
|
|
1180
|
+
/**
|
|
1181
|
+
* The idempotency rows behind a refund.
|
|
1182
|
+
*
|
|
1183
|
+
* 🛑 `STATE` is the claim's own lifecycle ("claimed" → settled), NOT a
|
|
1184
|
+
* postal state. `audit-route-nav-field-constants` matches a raw field
|
|
1185
|
+
* string against every constant that happens to share its VALUE, so it
|
|
1186
|
+
* suggested `ADDRESS_FIELDS.STATE` here — right string, wrong concept, and
|
|
1187
|
+
* taking the suggestion would have made the query read as address filtering.
|
|
1188
|
+
*/
|
|
1189
|
+
export const REFUND_CLAIM_FIELDS = {
|
|
1190
|
+
STATE: "state",
|
|
1191
|
+
CLAIMED_AT: "claimedAt",
|
|
1192
|
+
};
|
|
1193
|
+
// ============================================================================
|
|
1194
|
+
// PRICE OBSERVATIONS
|
|
1195
|
+
// ============================================================================
|
|
1196
|
+
/** Observed market prices, keyed by the taxonomy node they were seen under. */
|
|
1197
|
+
export const PRICE_OBSERVATION_FIELDS = {
|
|
1198
|
+
CATEGORY_ID: "categoryId",
|
|
1199
|
+
};
|
|
1200
|
+
// ============================================================================
|
|
1178
1201
|
// OAUTH STATE VALUES
|
|
1179
1202
|
// ============================================================================
|
|
1180
1203
|
export const OAUTH_STATE_VALUES = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeError } from "../errors/normalize.js";
|
|
2
2
|
import { toApiIssues } from "../errors/error-envelope.js";
|
|
3
|
-
import { HTTP_ERROR_CODES } from "../errors/error-mapping.js";
|
|
3
|
+
import { HTTP_ERROR_CODES, mapToHttpError } from "../errors/error-mapping.js";
|
|
4
4
|
/**
|
|
5
5
|
* createServerAction — @mohasinac/appkit/core
|
|
6
6
|
*
|
|
@@ -97,11 +97,27 @@ export function createServerAction(options) {
|
|
|
97
97
|
}
|
|
98
98
|
catch (err) {
|
|
99
99
|
void normalizeError(err);
|
|
100
|
-
|
|
100
|
+
/*
|
|
101
|
+
* 🛑 This never got the Rule #9 fix its sibling `wrapAction` did.
|
|
102
|
+
*
|
|
103
|
+
* It took `err.message` verbatim — unscrubbed for 5xx, so a thrown
|
|
104
|
+
* Node/bundler error's internals reached the client — and emitted NO
|
|
105
|
+
* `code`, so `toUserMessage(result.code)` downstream could only ever
|
|
106
|
+
* resolve to the generic sentence.
|
|
107
|
+
*
|
|
108
|
+
* It has no in-repo callers, but it is exported from the public barrel,
|
|
109
|
+
* so it is a trap for the next consumer rather than a live bug. Route it
|
|
110
|
+
* through the same mapper `wrapAction` uses: `mapToHttpError` scrubs 5xx
|
|
111
|
+
* messages in production and carries the real one in `internalMessage`
|
|
112
|
+
* for recorders only.
|
|
113
|
+
*/
|
|
114
|
+
const mapped = mapToHttpError(err, {
|
|
115
|
+
isProduction: process.env.NODE_ENV === "production",
|
|
116
|
+
});
|
|
101
117
|
logger.error(`[createServerAction] handler error in "${actionName}"`, {
|
|
102
118
|
err,
|
|
103
119
|
});
|
|
104
|
-
result = { ok: false, error: message };
|
|
120
|
+
result = { ok: false, code: mapped.code, error: mapped.message };
|
|
105
121
|
}
|
|
106
122
|
// -- 4. After hooks (per-action → global) ------------------------------
|
|
107
123
|
try {
|
|
@@ -13,7 +13,21 @@ import { normalizeError } from "../errors/normalize.js";
|
|
|
13
13
|
// db-firebase reaches firebase-admin only through `import type` plus its own
|
|
14
14
|
// bare-specifier runtime requires. See CLAUDE.md Root Cause #24.
|
|
15
15
|
import { getAdminDb } from "../providers/db-firebase/index.js";
|
|
16
|
-
|
|
16
|
+
/*
|
|
17
|
+
* 🛑 From `errors/database-error`, NEVER the `../errors` barrel.
|
|
18
|
+
*
|
|
19
|
+
* That barrel re-exports `error-handler`, which does
|
|
20
|
+
* `import { NextResponse } from "next/server.js"` — and its own comment above
|
|
21
|
+
* that line says "only import in server/API-route contexts". This module is
|
|
22
|
+
* reachable from the Firebase Functions entry
|
|
23
|
+
* (`jobs.js → repositories → unit-of-work`), so the barrel dragged Next's
|
|
24
|
+
* whole server runtime into the tsup bundle. It failed with four errors that
|
|
25
|
+
* name neither this file nor the barrel: two unresolved
|
|
26
|
+
* `react-server-dom-webpack/*` requires and two `.wasm` files from
|
|
27
|
+
* `@vercel/og`. Nothing in appkit's own typecheck can see it; only
|
|
28
|
+
* `npm --prefix functions run build` can. Root Cause #18.
|
|
29
|
+
*/
|
|
30
|
+
import { DatabaseError } from "../errors/database-error.js";
|
|
17
31
|
import { userRepository, tokenRepository, sessionRepository, } from "../features/auth/repository/index.js";
|
|
18
32
|
import { addressesRepository } from "../features/addresses/repository/addresses.repository.js";
|
|
19
33
|
import { cartRepository } from "../features/cart/repository/cart.repository.js";
|
|
@@ -42,6 +42,31 @@ function isZodLikeError(err) {
|
|
|
42
42
|
"issues" in err &&
|
|
43
43
|
Array.isArray(err.issues));
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The issues carried by a `ValidationError`, whichever of the two shapes the
|
|
47
|
+
* thrower used.
|
|
48
|
+
*
|
|
49
|
+
* 🛑 `new ValidationError(msg, parsed.error.issues)` silently lost everything.
|
|
50
|
+
* `ValidationError` forwards its second argument to `AppError.data`, and the
|
|
51
|
+
* old test was `isZodLikeError(err.data)` — which requires `"issues" in data`.
|
|
52
|
+
* An ARRAY has no `.issues` property (`"issues" in [] === false`), so passing
|
|
53
|
+
* `parsed.error.issues` (the obvious thing to pass, and what four routes do)
|
|
54
|
+
* produced a 400 with no field information at all. Only passing the whole
|
|
55
|
+
* `ZodError` worked.
|
|
56
|
+
*
|
|
57
|
+
* `media/finalize/route.ts` even carried a comment asserting the opposite —
|
|
58
|
+
* "carries `issues` through to the client, so the caller learns WHICH field it
|
|
59
|
+
* got wrong". It did not.
|
|
60
|
+
*
|
|
61
|
+
* Accept both, so the next caller cannot get it wrong either way.
|
|
62
|
+
*/
|
|
63
|
+
function issuesFromErrorData(data) {
|
|
64
|
+
if (Array.isArray(data))
|
|
65
|
+
return data.length > 0 ? data : undefined;
|
|
66
|
+
if (isZodLikeError(data))
|
|
67
|
+
return data.issues;
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
45
70
|
function isFirestoreLikeError(err) {
|
|
46
71
|
if (typeof err !== "object" || err === null)
|
|
47
72
|
return false;
|
|
@@ -110,7 +135,7 @@ function classifyError(err, opts) {
|
|
|
110
135
|
status: 400,
|
|
111
136
|
code: HTTP_ERROR_CODES.VALIDATION_FAILED,
|
|
112
137
|
message: err.message,
|
|
113
|
-
issues:
|
|
138
|
+
issues: issuesFromErrorData(err.data),
|
|
114
139
|
};
|
|
115
140
|
}
|
|
116
141
|
if (err instanceof AuthenticationError) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firestore / gRPC error-code predicates, in one place.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this file exists
|
|
5
|
+
*
|
|
6
|
+
* `isAlreadyExists` had been written THREE times — in the gateway-events,
|
|
7
|
+
* payment-cases and protection-claims repositories, two of them byte-identical
|
|
8
|
+
* — and each copy duck-typed the caught error the same way:
|
|
9
|
+
*
|
|
10
|
+
* const code = (err as { code?: unknown }).code;
|
|
11
|
+
*
|
|
12
|
+
* That is past the Rule of Three, and `audit-unknown-leakage` was reporting all
|
|
13
|
+
* six of those field reads: casting to `{ code?: unknown }` narrows the OBJECT
|
|
14
|
+
* and leaves the FIELD unknown, so every comparison below it is unchecked.
|
|
15
|
+
* Naming the shape once gives the comparisons a real type and leaves one
|
|
16
|
+
* implementation to fix when the SDK changes what it throws.
|
|
17
|
+
*
|
|
18
|
+
* ## 🛑 Why three checks and not one
|
|
19
|
+
*
|
|
20
|
+
* The Admin SDK has surfaced this condition as a numeric gRPC code, as a string
|
|
21
|
+
* code, and as a bare message, depending on version and transport. All three
|
|
22
|
+
* are tested, and the message test is in ADDITION to the codes rather than
|
|
23
|
+
* instead of them — a stringly-typed guard alone is what breaks silently on an
|
|
24
|
+
* SDK bump, and the failure mode is a create that reports success while having
|
|
25
|
+
* collided.
|
|
26
|
+
*/
|
|
27
|
+
/** gRPC status 6. The numeric form, which is what the Admin SDK usually sets. */
|
|
28
|
+
export declare const GRPC_ALREADY_EXISTS = 6;
|
|
29
|
+
/**
|
|
30
|
+
* Did Firestore refuse this write because the document already exists?
|
|
31
|
+
*
|
|
32
|
+
* The one question every `createWithId`-style insert needs answered, and the
|
|
33
|
+
* reason it matters is that "already there" is usually SUCCESS for an
|
|
34
|
+
* idempotent writer — a claim, a case or an inbox row that a retry re-sends.
|
|
35
|
+
* Treating it as a failure turns a safe retry into a 500.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isAlreadyExistsError(err: unknown): boolean;
|