@porulle/core 0.46.0 → 0.48.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -62,8 +62,8 @@
62
62
  "eslint": "^9.39.1",
63
63
  "typescript": "5.9.2",
64
64
  "vitest": "^3.2.4",
65
- "@porulle/eslint-config": "0.1.0",
66
- "@porulle/typescript-config": "0.1.0"
65
+ "@porulle/typescript-config": "0.1.0",
66
+ "@porulle/eslint-config": "0.1.0"
67
67
  },
68
68
  "publishConfig": {
69
69
  "access": "public"
@@ -46,6 +46,38 @@ function inTransactionHookContext(context: HookContext): HookContext {
46
46
  /** Default hook timeout: 20 seconds */
47
47
  const HOOK_TIMEOUT_MS = 20_000;
48
48
 
49
+ /**
50
+ * Warn above this. The timeout is the wrong instrument for the common case: a hook does not have
51
+ * to reach 20 s to be a problem, and by the time it does the damage is already spent.
52
+ *
53
+ * An in-transaction hook holds a pooled Postgres transaction for its whole duration. An
54
+ * after-commit one extends the invocation that wrote — on Workers a promise the invocation does
55
+ * not outlive, so the writer waits for it. `failures.ts` records what that costs unobserved: four
56
+ * connector suites took 578.23 s instead of 27.29 s and logged 51 `deliverWebhooks timed out after
57
+ * 20000ms`, exit 0, 22 of 22 passing. The only thing that disagreed was the wall clock.
58
+ *
59
+ * 100 ms matches Vendure's blocking-handler warning, and the remedy is the same one it names:
60
+ * move non-trivial work to the job queue and let the hook enqueue it.
61
+ */
62
+ const HOOK_SLOW_MS = 100;
63
+
64
+ function warnIfSlow(context: HookContext, hookName: string, startedAt: number, inTransaction: boolean): void {
65
+ const elapsed = Date.now() - startedAt;
66
+ if (elapsed <= HOOK_SLOW_MS) return;
67
+ context.logger.warn(
68
+ `After-hook "${hookName}" took ${elapsed}ms`,
69
+ {
70
+ hookName,
71
+ elapsedMs: elapsed,
72
+ inTransaction,
73
+ requestId: context.requestId,
74
+ hint: inTransaction
75
+ ? "Runs inside the writing transaction and holds its connection for this long. Enqueue the work instead."
76
+ : "Runs after commit but inside the same invocation, so the writer waits for it. Enqueue the work instead.",
77
+ },
78
+ );
79
+ }
80
+
49
81
  function withTimeout<T>(promiseOrValue: Promise<T> | T, timeoutMs: number, hookName: string): Promise<T> {
50
82
  const promise = Promise.resolve(promiseOrValue);
51
83
  return new Promise<T>((resolve, reject) => {
@@ -130,8 +162,10 @@ export async function runAfterHooks<TResult, TData = TResult>(
130
162
  );
131
163
 
132
164
  if (runsInTransaction(hook)) {
165
+ const startedAt = Date.now();
133
166
  try {
134
167
  await runHook();
168
+ warnIfSlow(context, hookName, startedAt, true);
135
169
  } catch (error) {
136
170
  const message = error instanceof Error ? error.message : String(error);
137
171
  errors.push({ hookName, message });
@@ -144,8 +178,10 @@ export async function runAfterHooks<TResult, TData = TResult>(
144
178
  }
145
179
 
146
180
  const deferred = deferAfterCommit(async () => {
181
+ const startedAt = Date.now();
147
182
  try {
148
183
  await runHook();
184
+ warnIfSlow(context, hookName, startedAt, false);
149
185
  } catch (error) {
150
186
  // Reported rather than collected: this runs after the commit, so the HookReport below has
151
187
  // already been returned to the caller and there is nowhere else for this failure to go.
@@ -161,8 +197,10 @@ export async function runAfterHooks<TResult, TData = TResult>(
161
197
  });
162
198
 
163
199
  if (!deferred) {
200
+ const startedAt = Date.now();
164
201
  try {
165
202
  await runHook();
203
+ warnIfSlow(context, hookName, startedAt, false);
166
204
  } catch (error) {
167
205
  const message = error instanceof Error ? error.message : String(error);
168
206
  errors.push({ hookName, message });