arcway 0.1.4 → 0.1.5

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": "arcway",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A convention-based framework for building modular monoliths with strict domain boundaries.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/server/context.js CHANGED
@@ -1,15 +1,67 @@
1
+ function trackDb(db) {
2
+ if (!db) return { db, stats: { queries: 0, durationMs: 0 } };
3
+ const stats = { queries: 0, durationMs: 0 };
4
+ const handler = {
5
+ get(target, prop, receiver) {
6
+ const value = Reflect.get(target, prop, receiver);
7
+ if (typeof value !== 'function') return value;
8
+ if (prop === 'raw' || prop === 'select' || prop === 'insert' || prop === 'update' ||
9
+ prop === 'delete' || prop === 'del' || prop === 'from' || prop === 'into' ||
10
+ prop === 'table' || prop === 'with' || prop === 'withRecursive') {
11
+ return (...args) => {
12
+ const builder = value.apply(target, args);
13
+ return wrapBuilder(builder, stats);
14
+ };
15
+ }
16
+ return value.bind ? value.bind(target) : value;
17
+ },
18
+ apply(target, thisArg, args) {
19
+ const builder = Reflect.apply(target, thisArg, args);
20
+ if (builder && typeof builder.then === 'function' && typeof builder.select === 'function') {
21
+ return wrapBuilder(builder, stats);
22
+ }
23
+ return builder;
24
+ },
25
+ };
26
+ const proxy = new Proxy(db, handler);
27
+ return { db: proxy, stats };
28
+ }
29
+
30
+ function wrapBuilder(builder, stats) {
31
+ const origThen = builder.then.bind(builder);
32
+ builder.then = (resolve, reject) => {
33
+ const start = performance.now();
34
+ return origThen(
35
+ (result) => {
36
+ stats.queries++;
37
+ stats.durationMs += performance.now() - start;
38
+ return resolve?.(result);
39
+ },
40
+ (err) => {
41
+ stats.queries++;
42
+ stats.durationMs += performance.now() - start;
43
+ if (reject) return reject(err);
44
+ throw err;
45
+ },
46
+ );
47
+ };
48
+ return builder;
49
+ }
50
+
1
51
  function buildContext(appContext, extras) {
52
+ const tracked = trackDb(appContext.db);
2
53
  const ctx = {
3
- db: appContext.db,
54
+ db: tracked.db,
4
55
  log: appContext.log,
5
56
  events: appContext.events,
6
57
  cache: appContext.cache,
7
58
  queue: appContext.queue,
8
59
  files: appContext.files,
9
60
  mail: appContext.mail,
61
+ _dbStats: tracked.stats,
10
62
  ...extras,
11
63
  };
12
64
  return ctx;
13
65
  }
14
66
 
15
- export { buildContext };
67
+ export { buildContext, trackDb };
@@ -146,6 +146,26 @@ class ApiRouter {
146
146
  );
147
147
  }
148
148
 
149
+ _emitCanonicalLog(ctx, { method, path, route, status, startTime, middlewareNames, error, session }) {
150
+ const durationMs = Date.now() - startTime;
151
+ const dbStats = ctx._dbStats ?? { queries: 0, durationMs: 0 };
152
+ const line = {
153
+ method,
154
+ path,
155
+ route,
156
+ status,
157
+ durationMs,
158
+ dbQueries: dbStats.queries,
159
+ dbDurationMs: Math.round(dbStats.durationMs),
160
+ middleware: middlewareNames,
161
+ };
162
+ if (session?.userId ?? session?.id) {
163
+ line.userId = session.userId ?? session.id;
164
+ }
165
+ if (error) line.error = error;
166
+ ctx.log.info('request', line);
167
+ }
168
+
149
169
  async init() {
150
170
  if (this._config.enabled === false) {
151
171
  this._log?.info('API: disabled');
@@ -214,14 +234,9 @@ class ApiRouter {
214
234
  } catch (err) {
215
235
  const errorMsg = toErrorMessage(err);
216
236
  ctx.log.error(`Handler error in ${route.method} ${route.pattern}`, { error: errorMsg });
217
- ctx.log.info('request', {
218
- method,
219
- path: pathname,
220
- route: route.pattern,
221
- status: 500,
222
- durationMs: Date.now() - startTime,
223
- middleware: middlewareNames,
224
- error: errorMsg,
237
+ this._emitCanonicalLog(ctx, {
238
+ method, path: pathname, route: route.pattern, status: 500,
239
+ startTime, middlewareNames, error: errorMsg, session: reqInfo.session,
225
240
  });
226
241
  sendJson(res, 500, {
227
242
  error: { code: ErrorCodes.HANDLER_ERROR, message: 'An internal error occurred' },
@@ -248,13 +263,9 @@ class ApiRouter {
248
263
  // ── Serialize + send ──
249
264
  const statusCode = response.status ?? (response.error ? 400 : 200);
250
265
  await serializeResponse(res, response, responseHeaders, statusCode);
251
- ctx.log.info('request', {
252
- method,
253
- path: pathname,
254
- route: route.pattern,
255
- status: statusCode,
256
- durationMs: Date.now() - startTime,
257
- middleware: middlewareNames,
266
+ this._emitCanonicalLog(ctx, {
267
+ method, path: pathname, route: route.pattern, status: statusCode,
268
+ startTime, middlewareNames, session: response.session !== void 0 ? response.session : reqInfo.session,
258
269
  });
259
270
 
260
271
  return true;