@porulle/core 0.10.5 → 0.10.6
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/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +12 -1
- package/dist/kernel/hooks/executor.d.ts.map +1 -1
- package/dist/kernel/hooks/executor.js +18 -1
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +8 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/src/interfaces/rest/routes/checkout.ts +12 -1
- package/src/kernel/hooks/executor.ts +19 -1
- package/src/runtime/server.ts +8 -0
package/package.json
CHANGED
|
@@ -200,6 +200,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
200
200
|
database: { db: kernel.database.db as PluginDb },
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
+
let pipelineStage = "validate-and-calculate";
|
|
203
204
|
try {
|
|
204
205
|
// Phase 1: DB transaction for validation — releases connection immediately after
|
|
205
206
|
const validated = await kernel.database.transaction(async (_tx) => {
|
|
@@ -214,6 +215,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
214
215
|
|
|
215
216
|
// Phase 2: Payment authorization — NO DB connection held while calling Stripe/etc.
|
|
216
217
|
// If Stripe takes 5s, the DB connection pool is not affected.
|
|
218
|
+
pipelineStage = "authorize-payment";
|
|
217
219
|
context.tx = null;
|
|
218
220
|
const processed = await runBeforeHooks(
|
|
219
221
|
paymentHooks,
|
|
@@ -225,6 +227,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
225
227
|
// SEC-07: resolve the order's customer server-side. A self-service actor
|
|
226
228
|
// can only attribute the order to its own profile; a client-supplied
|
|
227
229
|
// foreign customerId is ignored unless the actor is staff.
|
|
230
|
+
pipelineStage = "resolve-customer";
|
|
228
231
|
const customerUuid = await resolveCheckoutCustomerUuid(
|
|
229
232
|
kernel.services.customers,
|
|
230
233
|
actor,
|
|
@@ -281,6 +284,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
281
284
|
// Checkout is a trusted, already-server-priced pipeline (resolveCurrentPrices
|
|
282
285
|
// + promotions/tax) and reserves stock in its own after-hooks — so it hands
|
|
283
286
|
// the order primitive precomputed totals rather than re-deriving them.
|
|
287
|
+
pipelineStage = "create-order";
|
|
284
288
|
const order = await kernel.services.orders.create(orderPayload, actor, undefined, {
|
|
285
289
|
trustedPricing: true,
|
|
286
290
|
});
|
|
@@ -293,6 +297,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
293
297
|
}
|
|
294
298
|
|
|
295
299
|
if (order.ok && (processed.appliedPromotions?.length ?? 0) > 0) {
|
|
300
|
+
pipelineStage = "record-promotion-usage";
|
|
296
301
|
await kernel.services.promotions.recordUsage({
|
|
297
302
|
promotions: processed.appliedPromotions ?? [],
|
|
298
303
|
organizationId: order.value.organizationId,
|
|
@@ -304,6 +309,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
304
309
|
}
|
|
305
310
|
|
|
306
311
|
if (order.ok) {
|
|
312
|
+
pipelineStage = "report-tax-transaction";
|
|
307
313
|
await kernel.services.tax.reportTransaction({
|
|
308
314
|
transactionId: order.value.id,
|
|
309
315
|
transactionDate: new Date(),
|
|
@@ -336,6 +342,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
336
342
|
// Stash paymentMethodId for completeCheckout compensation chain
|
|
337
343
|
context.context.paymentMethodId = processed.paymentMethodId;
|
|
338
344
|
|
|
345
|
+
pipelineStage = "complete-checkout";
|
|
339
346
|
const afterReport = await runAfterHooks(
|
|
340
347
|
afterHooks,
|
|
341
348
|
null,
|
|
@@ -344,6 +351,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
344
351
|
context,
|
|
345
352
|
);
|
|
346
353
|
|
|
354
|
+
pipelineStage = "mark-cart-checked-out";
|
|
347
355
|
await kernel.services.cart.markAsCheckedOut(body.cartId, actor);
|
|
348
356
|
|
|
349
357
|
return c.json(
|
|
@@ -374,7 +382,10 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
374
382
|
},
|
|
375
383
|
);
|
|
376
384
|
// Always log the real error — hidden errors in checkout are unacceptable
|
|
377
|
-
const
|
|
385
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
386
|
+
const realMessage = rawMessage === "[object ErrorEvent]"
|
|
387
|
+
? `Checkout stage "${pipelineStage}" failed: ${rawMessage}`
|
|
388
|
+
: rawMessage;
|
|
378
389
|
const realStack = error instanceof Error ? error.stack : undefined;
|
|
379
390
|
console.error("[checkout] Pipeline failed:", { message: realMessage, stack: realStack, code: (error as Record<string, unknown>)?.code });
|
|
380
391
|
|
|
@@ -34,6 +34,24 @@ function withTimeout<T>(promiseOrValue: Promise<T> | T, timeoutMs: number, hookN
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function actionableHookError(error: unknown, hookName: string): Error {
|
|
38
|
+
if (error instanceof Error && error.message && error.message !== "[object ErrorEvent]") {
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
41
|
+
const candidate = error as { error?: unknown; cause?: unknown; message?: unknown } | null;
|
|
42
|
+
const nested = candidate?.error ?? candidate?.cause;
|
|
43
|
+
const nestedMessage = nested instanceof Error
|
|
44
|
+
? nested.message
|
|
45
|
+
: nested && typeof nested === "object" && typeof (nested as { message?: unknown }).message === "string"
|
|
46
|
+
? String((nested as { message: string }).message)
|
|
47
|
+
: undefined;
|
|
48
|
+
const directMessage = typeof candidate?.message === "string" && candidate.message !== "[object ErrorEvent]"
|
|
49
|
+
? candidate.message
|
|
50
|
+
: undefined;
|
|
51
|
+
const message = nestedMessage || directMessage || String(error);
|
|
52
|
+
return new Error(`Before-hook "${hookName}" failed: ${message}`, { cause: error });
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
export async function runBeforeHooks<T>(
|
|
38
56
|
hooks: BeforeHook<T>[],
|
|
39
57
|
data: T,
|
|
@@ -54,7 +72,7 @@ export async function runBeforeHooks<T>(
|
|
|
54
72
|
error: error instanceof Error ? error.message : String(error),
|
|
55
73
|
requestId: context.requestId,
|
|
56
74
|
});
|
|
57
|
-
throw error; //
|
|
75
|
+
throw actionableHookError(error, hookName); // Before hooks MUST succeed with actionable context.
|
|
58
76
|
}
|
|
59
77
|
}
|
|
60
78
|
return current;
|
package/src/runtime/server.ts
CHANGED
|
@@ -39,8 +39,16 @@ type ServerEnv = {
|
|
|
39
39
|
* Exposed for direct unit testing — see test/server-edge-runtime.test.ts.
|
|
40
40
|
*/
|
|
41
41
|
export function isNodeRuntime(): boolean {
|
|
42
|
+
const workerUserAgent = typeof navigator !== "undefined"
|
|
43
|
+
? navigator.userAgent
|
|
44
|
+
: undefined;
|
|
45
|
+
const hasWorkersSocketPair = "WebSocketPair" in globalThis;
|
|
46
|
+
if (workerUserAgent === "Cloudflare-Workers" || hasWorkersSocketPair) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
42
49
|
return (
|
|
43
50
|
typeof process !== "undefined" &&
|
|
51
|
+
typeof process.versions?.node === "string" &&
|
|
44
52
|
typeof process.on === "function" &&
|
|
45
53
|
typeof process.exit === "function"
|
|
46
54
|
);
|