@porulle/core 0.46.0 → 0.47.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.
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/kernel/hooks/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKpF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAKzE;AA4DD,wBAAsB,cAAc,CAAC,CAAC,EACpC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAmBZ;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,KAAK,GAAG,OAAO,EAC1D,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAClC,YAAY,EAAE,KAAK,GAAG,IAAI,EAC1B,eAAe,EAAE,OAAO,EACxB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,EACpB,iBAAiB,GAAE,CAAC,IAAI,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,OAAqB,GAC5E,OAAO,CAAC,UAAU,CAAC,CAkErB"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/kernel/hooks/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKpF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAKzE;AA4FD,wBAAsB,cAAc,CAAC,CAAC,EACpC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAmBZ;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,KAAK,GAAG,OAAO,EAC1D,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAClC,YAAY,EAAE,KAAK,GAAG,IAAI,EAC1B,eAAe,EAAE,OAAO,EACxB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,EACpB,iBAAiB,GAAE,CAAC,IAAI,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,OAAqB,GAC5E,OAAO,CAAC,UAAU,CAAC,CAwErB"}
@@ -31,6 +31,34 @@ function inTransactionHookContext(context) {
31
31
  }
32
32
  /** Default hook timeout: 20 seconds */
33
33
  const HOOK_TIMEOUT_MS = 20_000;
34
+ /**
35
+ * Warn above this. The timeout is the wrong instrument for the common case: a hook does not have
36
+ * to reach 20 s to be a problem, and by the time it does the damage is already spent.
37
+ *
38
+ * An in-transaction hook holds a pooled Postgres transaction for its whole duration. An
39
+ * after-commit one extends the invocation that wrote — on Workers a promise the invocation does
40
+ * not outlive, so the writer waits for it. `failures.ts` records what that costs unobserved: four
41
+ * connector suites took 578.23 s instead of 27.29 s and logged 51 `deliverWebhooks timed out after
42
+ * 20000ms`, exit 0, 22 of 22 passing. The only thing that disagreed was the wall clock.
43
+ *
44
+ * 100 ms matches Vendure's blocking-handler warning, and the remedy is the same one it names:
45
+ * move non-trivial work to the job queue and let the hook enqueue it.
46
+ */
47
+ const HOOK_SLOW_MS = 100;
48
+ function warnIfSlow(context, hookName, startedAt, inTransaction) {
49
+ const elapsed = Date.now() - startedAt;
50
+ if (elapsed <= HOOK_SLOW_MS)
51
+ return;
52
+ context.logger.warn(`After-hook "${hookName}" took ${elapsed}ms`, {
53
+ hookName,
54
+ elapsedMs: elapsed,
55
+ inTransaction,
56
+ requestId: context.requestId,
57
+ hint: inTransaction
58
+ ? "Runs inside the writing transaction and holds its connection for this long. Enqueue the work instead."
59
+ : "Runs after commit but inside the same invocation, so the writer waits for it. Enqueue the work instead.",
60
+ });
61
+ }
34
62
  function withTimeout(promiseOrValue, timeoutMs, hookName) {
35
63
  const promise = Promise.resolve(promiseOrValue);
36
64
  return new Promise((resolve, reject) => {
@@ -85,8 +113,10 @@ export async function runAfterHooks(hooks, originalData, committedResult, operat
85
113
  : { ...context, tx: null },
86
114
  }), HOOK_TIMEOUT_MS, hookName);
87
115
  if (runsInTransaction(hook)) {
116
+ const startedAt = Date.now();
88
117
  try {
89
118
  await runHook();
119
+ warnIfSlow(context, hookName, startedAt, true);
90
120
  }
91
121
  catch (error) {
92
122
  const message = error instanceof Error ? error.message : String(error);
@@ -99,8 +129,10 @@ export async function runAfterHooks(hooks, originalData, committedResult, operat
99
129
  continue;
100
130
  }
101
131
  const deferred = deferAfterCommit(async () => {
132
+ const startedAt = Date.now();
102
133
  try {
103
134
  await runHook();
135
+ warnIfSlow(context, hookName, startedAt, false);
104
136
  }
105
137
  catch (error) {
106
138
  // Reported rather than collected: this runs after the commit, so the HookReport below has
@@ -116,8 +148,10 @@ export async function runAfterHooks(hooks, originalData, committedResult, operat
116
148
  }
117
149
  });
118
150
  if (!deferred) {
151
+ const startedAt = Date.now();
119
152
  try {
120
153
  await runHook();
154
+ warnIfSlow(context, hookName, startedAt, false);
121
155
  }
122
156
  catch (error) {
123
157
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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 });