arcway 0.1.4 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcway",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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 || typeof db !== 'function' && typeof db !== 'object') 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 };
@@ -112,6 +112,15 @@ class Logger {
112
112
  return this.query({ level: 'error', limit, since });
113
113
  }
114
114
 
115
+ addContext(fields) {
116
+ if (!this._canonicalContext) this._canonicalContext = {};
117
+ Object.assign(this._canonicalContext, fields);
118
+ }
119
+
120
+ getCanonicalContext() {
121
+ return this._canonicalContext ?? null;
122
+ }
123
+
115
124
  extend(fields) {
116
125
  const child = Object.create(Logger.prototype);
117
126
  const hasFields = Object.keys(fields).length > 0;
@@ -123,6 +132,7 @@ class Logger {
123
132
  child._root = this._root ?? this;
124
133
  child._base = null;
125
134
  child._ring = null;
135
+ child._canonicalContext = null;
126
136
  return child;
127
137
  }
128
138
  }
@@ -146,6 +146,28 @@ 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 extra = ctx.log.getCanonicalContext?.() ?? {};
153
+ const line = {
154
+ method,
155
+ path,
156
+ route,
157
+ status,
158
+ durationMs,
159
+ dbQueries: dbStats.queries,
160
+ dbDurationMs: Math.round(dbStats.durationMs),
161
+ middleware: middlewareNames,
162
+ ...extra,
163
+ };
164
+ if (session?.userId ?? session?.id) {
165
+ line.userId = session.userId ?? session.id;
166
+ }
167
+ if (error) line.error = error;
168
+ ctx.log.info('request', line);
169
+ }
170
+
149
171
  async init() {
150
172
  if (this._config.enabled === false) {
151
173
  this._log?.info('API: disabled');
@@ -214,14 +236,9 @@ class ApiRouter {
214
236
  } catch (err) {
215
237
  const errorMsg = toErrorMessage(err);
216
238
  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,
239
+ this._emitCanonicalLog(ctx, {
240
+ method, path: pathname, route: route.pattern, status: 500,
241
+ startTime, middlewareNames, error: errorMsg, session: reqInfo.session,
225
242
  });
226
243
  sendJson(res, 500, {
227
244
  error: { code: ErrorCodes.HANDLER_ERROR, message: 'An internal error occurred' },
@@ -248,13 +265,9 @@ class ApiRouter {
248
265
  // ── Serialize + send ──
249
266
  const statusCode = response.status ?? (response.error ? 400 : 200);
250
267
  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,
268
+ this._emitCanonicalLog(ctx, {
269
+ method, path: pathname, route: route.pattern, status: statusCode,
270
+ startTime, middlewareNames, session: response.session !== void 0 ? response.session : reqInfo.session,
258
271
  });
259
272
 
260
273
  return true;