@mohasinac/appkit 4.60.0 → 4.61.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/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/adapters.d.ts +20 -2
- package/dist/_internal/server/features/orders/adapters.js +18 -0
- 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 +84 -57
- 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/features/promotions/schema.d.ts +66 -0
- package/dist/_internal/shared/features/promotions/schema.js +7 -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/client.d.ts +1 -0
- package/dist/client.js +3 -0
- package/dist/core/unit-of-work.js +15 -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/admin/components/AdminCouponEditorView.js +8 -0
- package/dist/features/admin/components/AdminGatewayEventsView.js +2 -2
- package/dist/features/admin/components/DataListingView.js +2 -0
- package/dist/features/admin/components/analytics/AdminStatCard.js +4 -1
- 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/auctions/components/MarketplaceAuctionCard.js +1 -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/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 +72 -45
- 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/obligations/repository/obligations.repository.js +10 -1
- package/dist/features/orders/schemas/firestore.d.ts +19 -0
- package/dist/features/orders/schemas/firestore.js +19 -0
- 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/MarketplaceBundleCard.js +1 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -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/schemas/coupon-form.d.ts +5 -0
- package/dist/features/seller/schemas/coupon-form.js +18 -0
- package/dist/features/shell/SectionForm.js +31 -9
- package/dist/features/sticker-trace/server.d.ts +2 -0
- package/dist/http/api-handler.js +4 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/next/routing/route-map.d.ts +0 -2
- package/dist/next/routing/route-map.js +10 -1
- package/dist/schemas/registry.d.ts +11 -6
- package/dist/server.js +7 -0
- package/dist/styles.css +51 -0
- package/dist/ui/components/PaginatedSelect.js +0 -0
- 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
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
"use server";
|
|
2
|
-
|
|
2
|
+
/*
|
|
3
|
+
* 🛑 From the DEFINING module, never "@mohasinac/appkit/server".
|
|
4
|
+
*
|
|
5
|
+
* appkit importing its own public server barrel is a self-reference that drags
|
|
6
|
+
* the whole server surface in behind it — `dist/server.js` reaches next/og,
|
|
7
|
+
* next/navigation, next/headers and driver.js. Twenty `actions.ts` files did
|
|
8
|
+
* this for two symbols, and because `obligationSweep` reaches one of them, the
|
|
9
|
+
* Firebase Functions tsup bundle (noExternal: appkit) had to resolve Next:
|
|
10
|
+
* it failed on two `react-server-dom-webpack/*` requires and two @vercel/og
|
|
11
|
+
* .wasm files, naming none of the responsible files. Root Cause #18.
|
|
12
|
+
*/
|
|
13
|
+
import { wrapAction } from "../../../shared/types/action-result.js";
|
|
3
14
|
/**
|
|
4
15
|
* processRefundAction — append a refund event to an order.
|
|
5
16
|
*
|
|
@@ -235,7 +246,8 @@ export async function processRefundAction(input) {
|
|
|
235
246
|
const isFull = wouldBeFullRefund(order, input.amount, siteSettings?.commissions?.gstPercent);
|
|
236
247
|
const event = {
|
|
237
248
|
refundId,
|
|
238
|
-
|
|
249
|
+
// Derived from the authoritative `isFull` above, never from a caller.
|
|
250
|
+
type: isFull ? "full" : "partial",
|
|
239
251
|
amount: input.amount,
|
|
240
252
|
...(input.itemIds?.length ? { itemIds: input.itemIds } : {}),
|
|
241
253
|
reasonCode: input.reasonCode,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActionResult } from "
|
|
1
|
+
import { type ActionResult } from "../../../shared/types/action-result";
|
|
2
2
|
export declare function createReviewAction(input: unknown): Promise<ActionResult<unknown>>;
|
|
3
3
|
export declare function replyToReviewAction(input: unknown): Promise<ActionResult<unknown>>;
|
|
4
4
|
export declare function deleteReviewAction(input: unknown): Promise<ActionResult<unknown>>;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
"use server";
|
|
2
|
-
|
|
2
|
+
/*
|
|
3
|
+
* 🛑 From the DEFINING module, never "@mohasinac/appkit/server".
|
|
4
|
+
*
|
|
5
|
+
* appkit importing its own public server barrel is a self-reference that drags
|
|
6
|
+
* the whole server surface in behind it — `dist/server.js` reaches next/og,
|
|
7
|
+
* next/navigation, next/headers and driver.js. Twenty `actions.ts` files did
|
|
8
|
+
* this for two symbols, and because `obligationSweep` reaches one of them, the
|
|
9
|
+
* Firebase Functions tsup bundle (noExternal: appkit) had to resolve Next:
|
|
10
|
+
* it failed on two `react-server-dom-webpack/*` requires and two @vercel/og
|
|
11
|
+
* .wasm files, naming none of the responsible files. Root Cause #18.
|
|
12
|
+
*/
|
|
13
|
+
import { wrapAction } from "../../../shared/types/action-result.js";
|
|
3
14
|
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers.js";
|
|
4
15
|
import { isAdminUser } from "../../../../features/auth/role-predicates.js";
|
|
5
16
|
import { reviewRepository, productRepository, userRepository, } from "../../../../repositories/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActionResult } from "
|
|
1
|
+
import { type ActionResult } from "../../../shared/types/action-result";
|
|
2
2
|
import { type WishlistItem } from "../../../../repositories";
|
|
3
3
|
export declare function addToWishlistAction(productId: string, extras?: {
|
|
4
4
|
productType?: WishlistItem["productType"];
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
3
|
-
|
|
3
|
+
/*
|
|
4
|
+
* 🛑 From the DEFINING module, never "@mohasinac/appkit/server".
|
|
5
|
+
*
|
|
6
|
+
* appkit importing its own public server barrel is a self-reference that drags
|
|
7
|
+
* the whole server surface in behind it — `dist/server.js` reaches next/og,
|
|
8
|
+
* next/navigation, next/headers and driver.js. Twenty `actions.ts` files did
|
|
9
|
+
* this for two symbols, and because `obligationSweep` reaches one of them, the
|
|
10
|
+
* Firebase Functions tsup bundle (noExternal: appkit) had to resolve Next:
|
|
11
|
+
* it failed on two `react-server-dom-webpack/*` requires and two @vercel/og
|
|
12
|
+
* .wasm files, naming none of the responsible files. Root Cause #18.
|
|
13
|
+
*/
|
|
14
|
+
import { wrapAction } from "../../../shared/types/action-result.js";
|
|
4
15
|
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers.js";
|
|
5
16
|
import { wishlistRepository, WishlistFullError, } from "../../../../repositories/index.js";
|
|
6
17
|
import { WISHLIST_MAX } from "../../../shared/features/wishlist/config.js";
|
|
@@ -279,7 +279,6 @@ async function performAction(ctx, obligation, order) {
|
|
|
279
279
|
}
|
|
280
280
|
const result = await processRefundAction({
|
|
281
281
|
orderId: order.id,
|
|
282
|
-
type: "full",
|
|
283
282
|
amount: capacity.remaining,
|
|
284
283
|
reasonCode: order.returnReasonCode ?? "not_as_described",
|
|
285
284
|
reasonNote: `Auto-resolved in the buyer's favour — ${policy.describe}`,
|
|
@@ -36,7 +36,8 @@ import { gatewayEventsRepository } from "../../../../features/gateway-events/rep
|
|
|
36
36
|
import { GATEWAY_EVENT_MAX_ATTEMPTS, } from "../../../../features/gateway-events/schemas/firestore.js";
|
|
37
37
|
import { paymentCasesRepository } from "../../../../features/payment-cases/repository/payment-cases.repository.js";
|
|
38
38
|
import { orderRepository } from "../../../../features/orders/repository/orders.repository.js";
|
|
39
|
-
import { REFUND_CLAIMS_COLLECTION } from "../../../../features/orders/schemas/firestore.js";
|
|
39
|
+
import { REFUND_CLAIMS_COLLECTION, REFUND_CLAIM_FIELDS, ORDER_COLLECTION, } from "../../../../features/orders/schemas/firestore.js";
|
|
40
|
+
import { ORDER_FIELDS } from "../../../../constants/field-names.js";
|
|
40
41
|
import { reconcileDisputes } from "../../features/payment-cases/disputes.js";
|
|
41
42
|
import { refundableBase, committedRefundTotal, MONEY_EPSILON, } from "../../../shared/checkout/refundable-base.js";
|
|
42
43
|
/** How far back R2 looks. Two days of overlap, so a run that fails is covered by the next. */
|
|
@@ -137,7 +138,81 @@ export async function reconcileStuckGatewayEvents(ctx) {
|
|
|
137
138
|
}
|
|
138
139
|
return { redispatched, escalated, reads: stuck.length };
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
+
/**
|
|
142
|
+
* "The gateway captured a different amount from the one the order records."
|
|
143
|
+
*
|
|
144
|
+
* Extracted from the sweep below, where it sat inside
|
|
145
|
+
* fn → `for record` → `for order` → `if` and the whole case literal was a
|
|
146
|
+
* brace too deep. Pure construction — no branching, and `recorded` is passed
|
|
147
|
+
* in rather than re-derived, so the one money rule still lives at the call
|
|
148
|
+
* site (Root Cause #75).
|
|
149
|
+
*/
|
|
150
|
+
async function openAmountMismatchCase(record, orderId, recorded, now) {
|
|
151
|
+
await paymentCasesRepository().openOrObserve({
|
|
152
|
+
kind: "amount_mismatch",
|
|
153
|
+
subjectId: `${record.id}:${orderId}`,
|
|
154
|
+
severity: "error",
|
|
155
|
+
title: `Captured ${record.amount} but recorded ${recorded} — ${orderId}`,
|
|
156
|
+
detail: [
|
|
157
|
+
`The gateway captured a different amount from the one the order records.`,
|
|
158
|
+
`Either the buyer was over-charged or the order under-states what was paid.`,
|
|
159
|
+
"",
|
|
160
|
+
`Gateway: ${record.gateway}`,
|
|
161
|
+
`Payment: ${record.id}`,
|
|
162
|
+
`Order: ${orderId}`,
|
|
163
|
+
`Captured: ${record.amount} ${record.currency}`,
|
|
164
|
+
`Order total: ${recorded}`,
|
|
165
|
+
`Difference: ${(record.amount - recorded).toFixed(2)}`,
|
|
166
|
+
].join("\n"),
|
|
167
|
+
entityType: "order",
|
|
168
|
+
entityId: orderId,
|
|
169
|
+
amountAtRisk: Math.abs(record.amount - recorded),
|
|
170
|
+
orderIds: [orderId],
|
|
171
|
+
gatewayPaymentId: record.id,
|
|
172
|
+
now,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* "Our refund total disagrees with the gateway's."
|
|
177
|
+
*
|
|
178
|
+
* Same extraction as `openAmountMismatchCase`. `ours` and `base` are both
|
|
179
|
+
* computed by the caller from the shared helpers, so this never re-derives a
|
|
180
|
+
* refund figure of its own.
|
|
181
|
+
*
|
|
182
|
+
* 🛑 The severity split is the whole point and must not be flattened: the
|
|
183
|
+
* gateway having refunded MORE than we recorded is money that left without a
|
|
184
|
+
* record (`error`); our side showing more is a refund reserved and never
|
|
185
|
+
* completed (`warning`).
|
|
186
|
+
*/
|
|
187
|
+
async function openRefundMismatchCase(record, orderId, ours, base, now) {
|
|
188
|
+
await paymentCasesRepository().openOrObserve({
|
|
189
|
+
kind: "refund_mismatch",
|
|
190
|
+
subjectId: `${record.id}:refunds`,
|
|
191
|
+
severity: record.amountRefunded > ours ? "error" : "warning",
|
|
192
|
+
title: `Refund total disagrees with the gateway — ${orderId}`,
|
|
193
|
+
detail: [
|
|
194
|
+
record.amountRefunded > ours
|
|
195
|
+
? `🛑 The gateway has refunded MORE than our records show. Money has left that we did not record.`
|
|
196
|
+
: `Our records show more refunded than the gateway did — a refund may have been reserved and never completed.`,
|
|
197
|
+
"",
|
|
198
|
+
`Gateway: ${record.gateway}`,
|
|
199
|
+
`Payment: ${record.id}`,
|
|
200
|
+
`Order: ${orderId}`,
|
|
201
|
+
`Gateway refunded: ${record.amountRefunded}`,
|
|
202
|
+
`Our committed refunds: ${ours}`,
|
|
203
|
+
`Refundable base: ${base}`,
|
|
204
|
+
record.refundStatus ? `Gateway refund status: ${record.refundStatus}` : null,
|
|
205
|
+
]
|
|
206
|
+
.filter((l) => l !== null)
|
|
207
|
+
.join("\n"),
|
|
208
|
+
entityType: "order",
|
|
209
|
+
entityId: orderId,
|
|
210
|
+
amountAtRisk: Math.abs(record.amountRefunded - ours),
|
|
211
|
+
orderIds: [orderId],
|
|
212
|
+
gatewayPaymentId: record.id,
|
|
213
|
+
now,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
141
216
|
async function reconcileGatewayPayments(ctx) {
|
|
142
217
|
const payment = getProviders().payment;
|
|
143
218
|
let examined = 0;
|
|
@@ -232,29 +307,7 @@ async function reconcileGatewayPayments(ctx) {
|
|
|
232
307
|
const recorded = order.totalPrice ?? 0;
|
|
233
308
|
if (Math.abs(recorded - record.amount) > MONEY_EPSILON) {
|
|
234
309
|
amountMismatch += 1;
|
|
235
|
-
await
|
|
236
|
-
kind: "amount_mismatch",
|
|
237
|
-
subjectId: `${record.id}:${order.id}`,
|
|
238
|
-
severity: "error",
|
|
239
|
-
title: `Captured ${record.amount} but recorded ${recorded} — ${order.id}`,
|
|
240
|
-
detail: [
|
|
241
|
-
`The gateway captured a different amount from the one the order records.`,
|
|
242
|
-
`Either the buyer was over-charged or the order under-states what was paid.`,
|
|
243
|
-
"",
|
|
244
|
-
`Gateway: ${record.gateway}`,
|
|
245
|
-
`Payment: ${record.id}`,
|
|
246
|
-
`Order: ${order.id}`,
|
|
247
|
-
`Captured: ${record.amount} ${record.currency}`,
|
|
248
|
-
`Order total: ${recorded}`,
|
|
249
|
-
`Difference: ${(record.amount - recorded).toFixed(2)}`,
|
|
250
|
-
].join("\n"),
|
|
251
|
-
entityType: "order",
|
|
252
|
-
entityId: order.id,
|
|
253
|
-
amountAtRisk: Math.abs(record.amount - recorded),
|
|
254
|
-
orderIds: [order.id],
|
|
255
|
-
gatewayPaymentId: record.id,
|
|
256
|
-
now: ctx.now,
|
|
257
|
-
});
|
|
310
|
+
await openAmountMismatchCase(record, order.id, recorded, ctx.now);
|
|
258
311
|
}
|
|
259
312
|
/*
|
|
260
313
|
* Refund check — the gateway's `amount_refunded` against our own
|
|
@@ -265,33 +318,7 @@ async function reconcileGatewayPayments(ctx) {
|
|
|
265
318
|
const ours = committedRefundTotal(order);
|
|
266
319
|
if (Math.abs(ours - record.amountRefunded) > MONEY_EPSILON) {
|
|
267
320
|
refundMismatch += 1;
|
|
268
|
-
await
|
|
269
|
-
kind: "refund_mismatch",
|
|
270
|
-
subjectId: `${record.id}:refunds`,
|
|
271
|
-
severity: record.amountRefunded > ours ? "error" : "warning",
|
|
272
|
-
title: `Refund total disagrees with the gateway — ${order.id}`,
|
|
273
|
-
detail: [
|
|
274
|
-
record.amountRefunded > ours
|
|
275
|
-
? `🛑 The gateway has refunded MORE than our records show. Money has left that we did not record.`
|
|
276
|
-
: `Our records show more refunded than the gateway did — a refund may have been reserved and never completed.`,
|
|
277
|
-
"",
|
|
278
|
-
`Gateway: ${record.gateway}`,
|
|
279
|
-
`Payment: ${record.id}`,
|
|
280
|
-
`Order: ${order.id}`,
|
|
281
|
-
`Gateway refunded: ${record.amountRefunded}`,
|
|
282
|
-
`Our committed refunds: ${ours}`,
|
|
283
|
-
`Refundable base: ${refundableBase(order)}`,
|
|
284
|
-
record.refundStatus ? `Gateway refund status: ${record.refundStatus}` : null,
|
|
285
|
-
]
|
|
286
|
-
.filter((l) => l !== null)
|
|
287
|
-
.join("\n"),
|
|
288
|
-
entityType: "order",
|
|
289
|
-
entityId: order.id,
|
|
290
|
-
amountAtRisk: Math.abs(record.amountRefunded - ours),
|
|
291
|
-
orderIds: [order.id],
|
|
292
|
-
gatewayPaymentId: record.id,
|
|
293
|
-
now: ctx.now,
|
|
294
|
-
});
|
|
321
|
+
await openRefundMismatchCase(record, order.id, ours, refundableBase(order), ctx.now);
|
|
295
322
|
}
|
|
296
323
|
}
|
|
297
324
|
}
|
|
@@ -321,8 +348,8 @@ async function reconcileStaleRefundClaims(ctx) {
|
|
|
321
348
|
try {
|
|
322
349
|
const snap = await ctx.db
|
|
323
350
|
.collection(REFUND_CLAIMS_COLLECTION)
|
|
324
|
-
.where(
|
|
325
|
-
.where(
|
|
351
|
+
.where(REFUND_CLAIM_FIELDS.STATE, "==", "claimed")
|
|
352
|
+
.where(REFUND_CLAIM_FIELDS.CLAIMED_AT, "<", cutoff)
|
|
326
353
|
.limit(50)
|
|
327
354
|
.get();
|
|
328
355
|
for (const doc of snap.docs) {
|
|
@@ -404,9 +431,9 @@ async function reconcileUnpaidCancellations(ctx) {
|
|
|
404
431
|
let stale = 0;
|
|
405
432
|
try {
|
|
406
433
|
const snap = await ctx.db
|
|
407
|
-
.collection(
|
|
408
|
-
.where(
|
|
409
|
-
.where(
|
|
434
|
+
.collection(ORDER_COLLECTION)
|
|
435
|
+
.where(ORDER_FIELDS.STATUS, "==", "cancelled")
|
|
436
|
+
.where(ORDER_FIELDS.REFUND_STATUS, "==", "pending")
|
|
410
437
|
.limit(100)
|
|
411
438
|
.get();
|
|
412
439
|
/*
|
|
@@ -14,7 +14,7 @@ import type { JobContext } from "../runtime/types";
|
|
|
14
14
|
* (This used to be shared with testerSandboxRefresh.ts, which was removed when
|
|
15
15
|
* the tester runner took over fixture state — see scheduled.ts.)
|
|
16
16
|
*/
|
|
17
|
-
export declare const TEST_DATA_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events"];
|
|
17
|
+
export declare const TEST_DATA_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events", "orders", "obligations", "protectionClaims"];
|
|
18
18
|
export interface TestDataCleanupOptions {
|
|
19
19
|
/** true = wipe every isTestData:true doc regardless of testDataExpiresAt (manual force-purge). */
|
|
20
20
|
force?: boolean;
|
|
@@ -41,7 +41,28 @@ import { BID_FIELDS } from "../../../../constants/field-names.js";
|
|
|
41
41
|
* is both `test` AND `isTestData`. `audit-seed-purpose` R6 now asserts every
|
|
42
42
|
* entry here declares the field, so the `offers` shape cannot return.
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
/*
|
|
45
|
+
* 🛑 Every schema that declares `isTestData` must appear here, or the field is
|
|
46
|
+
* WRITE-ONLY and the row is never swept — a flag that reads as "this will be
|
|
47
|
+
* cleaned up" and never is.
|
|
48
|
+
*
|
|
49
|
+
* `orders`, `obligations` and `protectionClaims` were added 2026-10-22 after
|
|
50
|
+
* `audit-seed-purpose`'s inverse rule found all three declaring the field with
|
|
51
|
+
* no entry. They are the propagation chain: an order carries the flag, writing
|
|
52
|
+
* an obligation copies it (`orders.repository.ts` → `repo.open({ …isTestData
|
|
53
|
+
* })`), and a claim inherits it in turn — so a test order left three
|
|
54
|
+
* generations of descendants behind that nothing would ever collect.
|
|
55
|
+
*/
|
|
56
|
+
export const TEST_DATA_COLLECTIONS = [
|
|
57
|
+
"categories",
|
|
58
|
+
"stores",
|
|
59
|
+
"products",
|
|
60
|
+
"blogPosts",
|
|
61
|
+
"events",
|
|
62
|
+
"orders",
|
|
63
|
+
"obligations",
|
|
64
|
+
"protectionClaims",
|
|
65
|
+
];
|
|
45
66
|
const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
|
|
46
67
|
export async function runTestDataCleanup(ctx, opts = {}) {
|
|
47
68
|
const cutoff = opts.force ? null : new Date();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { allocateShipmentCosts } from "../../../../features/shipments/utils/cost-allocation.js";
|
|
2
2
|
import { siteSettingsRepository } from "../../../../features/admin/repository/site-settings.repository.js";
|
|
3
|
+
import { safeRead } from "../../../../errors/safe-read.js";
|
|
3
4
|
/*
|
|
4
5
|
* ── D58: the admin saw 6 while this used 4 ───────────────────────────────
|
|
5
6
|
*
|
|
@@ -97,10 +98,21 @@ async function recomputeShipmentAllocation(shipmentId, ctx) {
|
|
|
97
98
|
* single shipment, not a sweep, so the read is bounded — and the
|
|
98
99
|
* alternative (keep ignoring the admin control) is the defect.
|
|
99
100
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
/*
|
|
102
|
+
* `safeRead`, not `.catch(() => undefined)`.
|
|
103
|
+
*
|
|
104
|
+
* The fallback is unchanged — a settings read that fails must not stop the
|
|
105
|
+
* allocation, and `settingsMaxHours` being absent simply means "no admin
|
|
106
|
+
* ceiling". What changes is that the failure is now RECORDED: a bare catch
|
|
107
|
+
* returning `undefined` is indistinguishable from an admin who never set a
|
|
108
|
+
* ceiling, so the labour cap could silently stop being enforced with
|
|
109
|
+
* nothing anywhere to say so. `installDegradedReadReporter` persists it.
|
|
110
|
+
*/
|
|
111
|
+
const laborSettings = await safeRead(() => siteSettingsRepository.getSingleton().then((s) => s.laborRate), {
|
|
112
|
+
route: "job:onShipmentAllocationSync",
|
|
113
|
+
key: "siteSettings:laborRate",
|
|
114
|
+
fallback: undefined,
|
|
115
|
+
});
|
|
104
116
|
const settingsMaxHours = laborSettings?.maxHoursPerDay;
|
|
105
117
|
const { perLot, totals } = allocateShipmentCosts({
|
|
106
118
|
lots: allocationInputs,
|
|
@@ -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),
|
|
@@ -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
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export { BUILT_IN_THEMES, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, REQUIRED_GRAD
|
|
|
31
31
|
export type { GradientKey, RequiredThemeToken, ThemeMode, ThemeRecord, } from "./tokens/themes";
|
|
32
32
|
export { ConfirmDeleteModal } from "./ui/components/ConfirmDeleteModal";
|
|
33
33
|
export { EmptyState } from "./ui/components/EmptyState";
|
|
34
|
+
export { FilterChipGroup } from "./ui/components/FilterChipGroup";
|
|
34
35
|
export { Skeleton } from "./ui/components/Skeleton";
|
|
35
36
|
export { IconBox } from "./ui/components/IconBox";
|
|
36
37
|
export type { IconBoxProps, IconBoxRounded, IconBoxSize, IconBoxTone, } from "./ui/components/IconBox";
|
package/dist/client.js
CHANGED
|
@@ -52,6 +52,9 @@ export { BUILT_IN_THEMES, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, REQUIRED_GRAD
|
|
|
52
52
|
export { ConfirmDeleteModal } from "./ui/components/ConfirmDeleteModal.js";
|
|
53
53
|
// S-STORE — pure-UI primitives + Seller view consumed by new dashboard pages
|
|
54
54
|
export { EmptyState } from "./ui/components/EmptyState.js";
|
|
55
|
+
/* FilterChipGroup — a client UI primitive that existed, was used by
|
|
56
|
+
`ProtectionClaimsClient`, and was never exported here. */
|
|
57
|
+
export { FilterChipGroup } from "./ui/components/FilterChipGroup.js";
|
|
55
58
|
export { Skeleton } from "./ui/components/Skeleton.js";
|
|
56
59
|
// IconBox — square-icon container primitive; was exported from index.ts only,
|
|
57
60
|
// missing here even though CLAUDE.md documents it as a client-usable pattern.
|
|
@@ -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";
|