thatcher 1.0.7 → 1.0.34

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.
Files changed (174) hide show
  1. package/README.md +4 -16
  2. package/package.json +12 -18
  3. package/src/adapters/google-auth.js +4 -43
  4. package/src/adapters/google-drive.js +12 -67
  5. package/src/app/api/audit/dashboard/route.js +14 -11
  6. package/src/app/api/audit/logs/route.js +2 -2
  7. package/src/app/api/audit/permissions/[id]/route.js +6 -3
  8. package/src/app/api/audit/permissions/route.js +10 -7
  9. package/src/app/api/audit/permissions/stats/route.js +8 -5
  10. package/src/app/api/audit/route.js +4 -4
  11. package/src/app/api/audit/stats/route.js +1 -1
  12. package/src/app/api/auth/google/callback/route.js +5 -2
  13. package/src/app/api/auth/google/route.js +4 -1
  14. package/src/app/api/auth/login/route.js +5 -2
  15. package/src/app/api/auth/mwr-bridge/route.js +11 -21
  16. package/src/app/api/auth/password-reset/route.js +15 -14
  17. package/src/app/api/cron/trigger/route.js +4 -1
  18. package/src/app/api/debug/[[...path]]/route.js +6 -5
  19. package/src/app/api/debug/sqlite/route.js +7 -12
  20. package/src/app/api/debug/sync/route.js +5 -5
  21. package/src/app/api/domains/route.js +4 -1
  22. package/src/app/api/email/allocate/batch/route.js +19 -29
  23. package/src/app/api/email/allocate/route.js +16 -17
  24. package/src/app/api/email/receive/route.js +31 -42
  25. package/src/app/api/email/send/route.js +28 -16
  26. package/src/app/api/email/unallocated/route.js +10 -13
  27. package/src/app/api/files/[id]/route.js +4 -1
  28. package/src/app/api/formula/[[...path]]/route.js +0 -4
  29. package/src/app/api/health/route.js +10 -9
  30. package/src/app/api/metrics/route.js +1 -1
  31. package/src/cli.js +4 -4
  32. package/src/config/config-loader.js +1 -21
  33. package/src/config/constants.js +0 -35
  34. package/src/config/env.js +0 -30
  35. package/src/config/spec-helpers.js +6 -95
  36. package/src/engine.js +23 -0
  37. package/src/engine.server.js +8 -78
  38. package/src/index.js +42 -67
  39. package/src/lib/alert-manager.js +7 -3
  40. package/src/lib/api-helpers.js +4 -3
  41. package/src/lib/api.js +10 -10
  42. package/src/lib/audit-logger-enhanced.js +77 -83
  43. package/src/lib/auth-middleware.js +1 -32
  44. package/src/lib/auth-route-helpers.js +5 -0
  45. package/src/lib/busybase-adapter.js +0 -5
  46. package/src/lib/busybase-audit-reads.js +142 -0
  47. package/src/lib/busybase-audit.js +41 -0
  48. package/src/lib/busybase-lucia-adapter.js +72 -0
  49. package/src/lib/busybase-store.js +338 -0
  50. package/src/lib/config-field-helpers.js +0 -17
  51. package/src/lib/config-generator-engine.js +19 -36
  52. package/src/lib/config-helpers.js +0 -21
  53. package/src/lib/crud-action-helpers.js +2 -2
  54. package/src/lib/crud-factory.js +1 -16
  55. package/src/lib/crud-handlers.js +17 -52
  56. package/src/lib/csrf-protection.js +5 -2
  57. package/src/lib/date-utils.js +3 -127
  58. package/src/lib/debug-registry.js +0 -5
  59. package/src/lib/domain-loader.js +4 -1
  60. package/src/lib/email-sender.js +25 -23
  61. package/src/lib/error-handler.js +5 -41
  62. package/src/lib/error-recovery.js +10 -7
  63. package/src/lib/error-resilience.js +7 -5
  64. package/src/lib/events-engine.js +9 -6
  65. package/src/lib/export-sink.js +0 -5
  66. package/src/lib/field-iterator.js +7 -76
  67. package/src/lib/field-registry.js +0 -22
  68. package/src/lib/generic-crud-handler.js +0 -16
  69. package/src/lib/hook-engine.js +5 -53
  70. package/src/lib/hot-reload/checkpoint.js +5 -2
  71. package/src/lib/hot-reload/promise-container.js +7 -3
  72. package/src/lib/hot-reload/route-wrapper.js +4 -1
  73. package/src/lib/hot-reload/supervisor.js +6 -3
  74. package/src/lib/hyperformula-service.js +0 -5
  75. package/src/lib/id-helpers.js +10 -0
  76. package/src/lib/index.js +4 -7
  77. package/src/lib/log-aggregator.js +0 -1
  78. package/src/lib/logger.js +0 -15
  79. package/src/lib/metrics-collector.js +2 -46
  80. package/src/lib/next-compat.js +15 -1
  81. package/src/lib/observability-bootstrap.js +0 -5
  82. package/src/lib/perf-monitor.js +4 -1
  83. package/src/lib/perf-profiler.js +0 -5
  84. package/src/lib/query-string-adapter.js +5 -47
  85. package/src/lib/realtime-server.js +4 -26
  86. package/src/lib/request-tracing.js +0 -5
  87. package/src/lib/resource-monitor.js +4 -1
  88. package/src/lib/response-formatter.js +0 -38
  89. package/src/lib/route-helpers.js +3 -1
  90. package/src/lib/safe-json.js +5 -1
  91. package/src/lib/server-bootstrap.js +10 -9
  92. package/src/lib/stage-pipeline.js +2 -2
  93. package/src/lib/state-transport-client.js +4 -1
  94. package/src/lib/static-server.js +2 -2
  95. package/src/lib/status-helpers.js +2 -51
  96. package/src/lib/tracing.js +0 -5
  97. package/src/lib/utils.js +6 -67
  98. package/src/lib/validate.js +5 -88
  99. package/src/lib/validators.js +0 -38
  100. package/src/lib/with-audit-logging.js +1 -1
  101. package/src/lib/workflow-engine.js +10 -77
  102. package/src/lib/xstate-workflow-engine.js +3 -9
  103. package/src/plugins/index.js +5 -3
  104. package/src/server/server.js +26 -27
  105. package/src/services/collaborator-role.service.js +21 -77
  106. package/src/services/email-sender.js +7 -34
  107. package/src/services/notification-engine.js +12 -33
  108. package/src/services/permission.service.js +10 -73
  109. package/src/ui/advanced-search-renderer.js +3 -2
  110. package/src/ui/advanced-widgets.js +1 -1
  111. package/src/ui/auth-pages.js +10 -9
  112. package/src/ui/auth-styles.js +1 -1
  113. package/src/ui/client.js +1 -1
  114. package/src/ui/component-engine.js +1 -1
  115. package/src/ui/dashboard-renderer.js +5 -5
  116. package/src/ui/dialog-engine.js +6 -9
  117. package/src/ui/dialog-factory.js +2 -2
  118. package/src/ui/engagement-cards.js +7 -7
  119. package/src/ui/engagement-dialogs.js +2 -3
  120. package/src/ui/engagement-grid-renderer.js +20 -19
  121. package/src/ui/entity-renderer.js +56 -42
  122. package/src/ui/file-dialogs.js +25 -9
  123. package/src/ui/flexup-report-renderer.js +21 -18
  124. package/src/ui/format-helpers.js +27 -28
  125. package/src/ui/highlight-threading-renderer.js +9 -9
  126. package/src/ui/job-management-renderer.js +18 -16
  127. package/src/ui/layout.js +51 -25
  128. package/src/ui/ml-console-renderer.js +15 -16
  129. package/src/ui/monitoring-dashboard-client.js +9 -8
  130. package/src/ui/notifications-renderer.js +11 -10
  131. package/src/ui/page-handler-admin.js +32 -23
  132. package/src/ui/page-handler-helpers.js +27 -23
  133. package/src/ui/page-handler-reviews.js +71 -35
  134. package/src/ui/page-handler-rfi.js +28 -14
  135. package/src/ui/page-handler.js +65 -48
  136. package/src/ui/picker-dialogs.js +26 -19
  137. package/src/ui/render-helpers.js +84 -46
  138. package/src/ui/review-comparison-renderer.js +8 -7
  139. package/src/ui/review-detail-panels.js +8 -9
  140. package/src/ui/review-detail-renderer.js +3 -3
  141. package/src/ui/review-detail-script.js +13 -12
  142. package/src/ui/review-mwr-renderer.js +6 -5
  143. package/src/ui/review-renderer.js +7 -6
  144. package/src/ui/review-widgets.js +3 -3
  145. package/src/ui/rfi-detail-renderer.js +37 -20
  146. package/src/ui/rfi-report-renderer.js +14 -9
  147. package/src/ui/settings-renderer-advanced.js +21 -18
  148. package/src/ui/settings-renderer-advanced2.js +20 -19
  149. package/src/ui/settings-renderer-teams.js +4 -4
  150. package/src/ui/settings-renderer.js +4 -4
  151. package/src/ui/spacing-system.js +5 -2
  152. package/src/ui/standalone-login.js +2 -2
  153. package/src/ui/styles.css +2 -1
  154. package/src/ui/styles2.css +75 -5
  155. package/src/ui/test-page.js +4 -4
  156. package/src/ui/widgets.js +20 -15
  157. package/src/lib/audit-logger.js +0 -193
  158. package/src/lib/database-core.js +0 -255
  159. package/src/lib/database-migrations.js +0 -350
  160. package/src/lib/db-backup.js +0 -97
  161. package/src/lib/error-boundary.js +0 -134
  162. package/src/lib/errors.js +0 -69
  163. package/src/lib/health-monitor.js +0 -134
  164. package/src/lib/index-optimizer.js +0 -96
  165. package/src/lib/query-engine-write.js +0 -221
  166. package/src/lib/query-engine.js +0 -446
  167. package/src/ui/collaboration-dialogs.js +0 -31
  168. package/src/ui/global-tags.js +0 -144
  169. package/src/ui/idle-logout.js +0 -156
  170. package/src/ui/letter-dialogs.js +0 -37
  171. package/src/ui/password-reset-page.js +0 -146
  172. package/src/ui/perf-renderer.js +0 -71
  173. package/src/ui/rfi-renderer.js +0 -194
  174. package/src/ui/validation-ui.js +0 -147
package/src/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export * from '@/lib/query-engine';
1
+ export * from '@/lib/busybase-store';
2
2
  export * from '@/lib/validate';
3
3
  export * from '@/lib/field-types';
4
4
  export * from '@/lib/field-iterator';
@@ -17,17 +17,14 @@ export {
17
17
  UnauthorizedError,
18
18
  ConflictError,
19
19
  DatabaseError,
20
- ExternalAPIError,
21
- errorHandler,
22
- apiErrorHandler,
23
20
  normalizeError,
24
21
  formatErrorResponse,
25
22
  createErrorLogger,
26
23
  } from '@/lib/error-handler';
27
24
  export { createApiHandler } from '@/lib/api';
28
- export * from '@/lib/errors';
29
- export { getDatabase, migrate, genId, now } from '@/lib/database-core';
30
- export { logAction, getAuditHistory, getEntityAuditTrail, getActionStats, getUserStats } from '@/lib/audit-logger';
25
+ export { genId, now } from '@/lib/id-helpers';
26
+ export { setBusyBaseClient } from '@/lib/busybase-store';
27
+ export { logAction } from '@/lib/busybase-audit';
31
28
  export * from '@/lib/realtime-server';
32
29
  export * from '@/lib/hook-engine';
33
30
  export * from '@/lib/events-engine';
@@ -32,7 +32,6 @@ function log(level, message, metadata = {}) {
32
32
  console.error(prefix, message, metaStr)
33
33
  } else if (level === 'warn') {
34
34
  console.warn(prefix, message, metaStr)
35
- } else {
36
35
  }
37
36
  }
38
37
 
package/src/lib/logger.js CHANGED
@@ -1,8 +1,3 @@
1
- /**
2
- * Structured Logger - Consistent logging across the SDK
3
- * Simple wrapper with prefix support
4
- */
5
-
6
1
  const LOG_PREFIXES = {
7
2
  API: '[API]',
8
3
  DB: '[DB]',
@@ -17,11 +12,6 @@ const LOG_PREFIXES = {
17
12
  workflow: '[Workflow]',
18
13
  };
19
14
 
20
- /**
21
- * Create a logger with prefix
22
- * @param {string} prefix - Log prefix (from LOG_PREFIXES or custom)
23
- * @returns {object} Logger with methods
24
- */
25
15
  export function createLogger(prefix) {
26
16
  const logger = {
27
17
  error: (msg, meta = {}) => {
@@ -43,11 +33,6 @@ export function createLogger(prefix) {
43
33
  return logger;
44
34
  }
45
35
 
46
- /**
47
- * Get prefixed logger
48
- * @param {string} key - Key from LOG_PREFIXES
49
- * @returns {object}
50
- */
51
36
  export function getLogger(key) {
52
37
  return createLogger(LOG_PREFIXES[key] || `[${key.toUpperCase()}]`);
53
38
  }
@@ -1,6 +1,4 @@
1
- /**
2
- * Metrics Collector - Request metrics and stats
3
- *
1
+ /*
4
2
  * Thatcher keeps its original lightweight request-summary API
5
3
  * (recordRequest/getMetrics/getSummary/resetMetrics) for backward compat.
6
4
  * Merged in the richer per-channel collector API consumed by
@@ -18,13 +16,6 @@ const metrics = {
18
16
 
19
17
  let _requestCount = 0;
20
18
 
21
- /**
22
- * Record a request
23
- * @param {string} endpoint
24
- * @param {string} method
25
- * @param {number} durationMs
26
- * @param {number} statusCode
27
- */
28
19
  export function recordRequest(endpoint, method, durationMs, statusCode) {
29
20
  _requestCount++;
30
21
 
@@ -38,17 +29,14 @@ export function recordRequest(endpoint, method, durationMs, statusCode) {
38
29
 
39
30
  metrics.requests.push(record);
40
31
 
41
- // Keep last 1000 requests
42
32
  if (metrics.requests.length > 1000) {
43
33
  metrics.requests.shift();
44
34
  }
45
35
 
46
- // Track slow queries (>500ms)
47
36
  if (durationMs > 500) {
48
37
  metrics.slowQueries.push(record);
49
38
  }
50
39
 
51
- // Track errors
52
40
  if (statusCode >= 400) {
53
41
  metrics.errors.push(record);
54
42
  }
@@ -63,16 +51,12 @@ export function recordRequest(endpoint, method, durationMs, statusCode) {
63
51
  if (arr.length > 1000) arr.shift();
64
52
  }
65
53
 
66
- /**
67
- * Get all metrics
68
- * @returns {object}
69
- */
70
54
  export function getMetrics() {
71
55
  const now = Date.now();
72
56
  const uptimeSec = Math.floor((now - metrics.startTime) / 1000);
73
57
 
74
58
  const requests = metrics.requests;
75
- const recent = requests.filter(r => now - r.timestamp < 60000); // last minute
59
+ const recent = requests.filter(r => now - r.timestamp < 60000);
76
60
 
77
61
  const avgDuration = recent.length
78
62
  ? recent.reduce((sum, r) => sum + r.durationMs, 0) / recent.length
@@ -93,10 +77,6 @@ export function getMetrics() {
93
77
  };
94
78
  }
95
79
 
96
- /**
97
- * Get summary statistics
98
- * @returns {object}
99
- */
100
80
  export function getSummary() {
101
81
  const m = getMetrics();
102
82
  return {
@@ -106,9 +86,6 @@ export function getSummary() {
106
86
  };
107
87
  }
108
88
 
109
- /**
110
- * Reset metrics (for testing)
111
- */
112
89
  export function resetMetrics() {
113
90
  metrics.requests = [];
114
91
  metrics.slowQueries = [];
@@ -135,9 +112,6 @@ const channels = {
135
112
  const errorCounts = new Map();
136
113
  const resourceSamples = [];
137
114
 
138
- /**
139
- * Record an error against an endpoint.
140
- */
141
115
  export function recordError(path, method, error, stack) {
142
116
  const key = `${method}:${path}`;
143
117
  if (!channels.errors.has(key)) {
@@ -151,9 +125,6 @@ export function recordError(path, method, error, stack) {
151
125
  errorCounts.set(countKey, (errorCounts.get(countKey) || 0) + 1);
152
126
  }
153
127
 
154
- /**
155
- * Record a database operation timing.
156
- */
157
128
  export function recordDatabase(operation, duration, query) {
158
129
  const key = operation;
159
130
  if (!channels.database.has(key)) {
@@ -164,17 +135,11 @@ export function recordDatabase(operation, duration, query) {
164
135
  if (arr.length > 1000) arr.shift();
165
136
  }
166
137
 
167
- /**
168
- * Record a resource sample (cpu/memory/disk).
169
- */
170
138
  export function recordResource(cpu, memory, disk) {
171
139
  resourceSamples.push({ cpu, memory, disk, ts: Date.now() });
172
140
  if (resourceSamples.length > 1000) resourceSamples.shift();
173
141
  }
174
142
 
175
- /**
176
- * Record a custom metric value with optional tags.
177
- */
178
143
  export function recordCustom(name, value, tags = {}) {
179
144
  const key = name;
180
145
  if (!channels.custom.has(key)) {
@@ -192,9 +157,6 @@ function getPercentile(values, percentile) {
192
157
  return sorted[Math.max(0, index)];
193
158
  }
194
159
 
195
- /**
196
- * Compute count/min/max/avg/p50/p95/p99 stats over a data array.
197
- */
198
160
  export function getStats(dataArray, field = 'duration') {
199
161
  if (!dataArray || dataArray.length === 0) {
200
162
  return { count: 0, min: 0, max: 0, avg: 0, p50: 0, p95: 0, p99: 0 };
@@ -257,9 +219,6 @@ export function getResourceMetrics() {
257
219
  };
258
220
  }
259
221
 
260
- /**
261
- * Aggregate snapshot across all channels.
262
- */
263
222
  export function getAllMetrics() {
264
223
  return {
265
224
  requests: getRequestMetrics(),
@@ -273,9 +232,6 @@ export function getAllMetrics() {
273
232
  };
274
233
  }
275
234
 
276
- /**
277
- * Clear the per-channel collector state.
278
- */
279
235
  export function clearMetrics() {
280
236
  channels.requests.clear();
281
237
  channels.errors.clear();
@@ -1,9 +1,23 @@
1
1
  const MAX_BODY_SIZE = 10 * 1024 * 1024;
2
2
 
3
+ // Wrap node's raw (plain-object, lowercased) headers so both the Fetch-style
4
+ // `headers.get('x')` API and direct `headers['x']` index access work. Node
5
+ // already lowercases header names; `.get()` lowercases its argument to match.
6
+ function wrapHeaders(raw) {
7
+ const h = raw || {};
8
+ return new Proxy(h, {
9
+ get(target, prop) {
10
+ if (prop === 'get') return (name) => target[String(name).toLowerCase()] ?? null;
11
+ if (prop === 'has') return (name) => String(name).toLowerCase() in target;
12
+ return target[prop];
13
+ },
14
+ });
15
+ }
16
+
3
17
  export class NextRequest {
4
18
  constructor(req, body, url) {
5
19
  this.method = req.method;
6
- this.headers = req.headers;
20
+ this.headers = wrapHeaders(req.headers);
7
21
  this.url = url;
8
22
  this.body = body;
9
23
  }
@@ -1,8 +1,3 @@
1
- /**
2
- * Observability Bootstrap - Initializes all observability subsystems on server startup
3
- * Starts tracing, registers debug exposures, begins metric collection
4
- */
5
-
6
1
  import { tracer } from './tracing.js';
7
2
  import { perfProfiler } from './perf-profiler.js';
8
3
  import { debugRegistry } from './debug-registry.js';
@@ -1,3 +1,6 @@
1
+ import { createLogger } from './logger.js';
2
+
3
+ const log = createLogger('[Perf]');
1
4
  const metrics = new Map()
2
5
  const thresholds = { render: 100, query: 50, api: 200, total: 500 }
3
6
  let enabled = process.env.PERF_MONITOR !== 'false'
@@ -49,7 +52,7 @@ export function checkThreshold(key, value) {
49
52
  if (!enabled) return true
50
53
  const limit = thresholds[key]
51
54
  if (limit && value > limit) {
52
- console.warn(`[PERF] ${key} exceeded threshold: ${value.toFixed(2)}ms > ${limit}ms`)
55
+ log.warn(`${key} exceeded threshold: ${value.toFixed(2)}ms > ${limit}ms`)
53
56
  return false
54
57
  }
55
58
  return true
@@ -1,8 +1,3 @@
1
- /**
2
- * Performance Profiler - Threshold-based profiling with regression detection
3
- * Per-operation p50/p95/p99 statistics with automatic alerting
4
- */
5
-
6
1
  import { createLogger } from './logger.js';
7
2
 
8
3
  const logger = createLogger('[PerfProfiler]');
@@ -1,17 +1,9 @@
1
- /**
2
- * Query String Adapter - Parse URL query parameters into typed objects
3
- * Adapted from moonlanding/src/lib/query-string-adapter.js
4
- */
1
+ // Adapted from moonlanding/src/lib/query-string-adapter.js
5
2
 
6
3
  // Thatcher uses a fixed default page size (no async config-engine lookup);
7
4
  // keeps QueryAdapter helpers synchronous to match existing call sites.
8
5
  const DEFAULT_PAGE_SIZE = 50;
9
6
 
10
- /**
11
- * Parse query string from URL
12
- * @param {object} request - Fetch Request or NextRequest-like
13
- * @returns {Promise<{q?: string, page?: number, pageSize?: number, filters?: object, sort?: object}>}
14
- */
15
7
  export async function parseQuery(request) {
16
8
  const url = request.url || (request._url ? `http://localhost${request._url}` : 'http://localhost/');
17
9
  const searchParams = new URL(url).searchParams;
@@ -45,11 +37,6 @@ export async function parseQuery(request) {
45
37
  return result;
46
38
  }
47
39
 
48
- /**
49
- * Coerce string to appropriate type
50
- * @param {string} value
51
- * @returns {any}
52
- */
53
40
  function coerceValue(value) {
54
41
  // Lowercase boolean strings
55
42
  if (value === 'true') return true;
@@ -63,11 +50,6 @@ function coerceValue(value) {
63
50
  return value;
64
51
  }
65
52
 
66
- /**
67
- * Get default value for config key
68
- * @param {string} key
69
- * @returns {any}
70
- */
71
53
  export function getDefault(key) {
72
54
  const defaults = {
73
55
  page: 1,
@@ -81,23 +63,14 @@ export function getDefault(key) {
81
63
  return defaults[key] ?? null;
82
64
  }
83
65
 
84
- /**
85
- * QueryAdapter - static helpers for parsing and building query strings.
86
- * Ported from moonlanding; methods kept synchronous to match thatcher call sites
87
- * (e.g. api.js destructures QueryAdapter.fromSearchParams(...) without await).
88
- */
66
+ // Ported from moonlanding; methods kept synchronous to match thatcher call sites
67
+ // (e.g. api.js destructures QueryAdapter.fromSearchParams(...) without await).
89
68
  export class QueryAdapter {
90
- /**
91
- * Parse a Request-like object. Delegates to thatcher's parseQuery so the
92
- * returned shape ({ q, page, pageSize, filters, sort }) stays consistent.
93
- */
69
+ // Delegates to thatcher's parseQuery so the returned shape stays consistent.
94
70
  static parse(request) {
95
71
  return parseQuery(request);
96
72
  }
97
73
 
98
- /**
99
- * Extract non-reserved filter params from a URLSearchParams instance.
100
- */
101
74
  static extractFilters(searchParams) {
102
75
  const filters = {};
103
76
  const reserved = new Set(['q', 'page', 'pageSize', 'page_size', 'action', 'limit', 'offset', 'sort', 'sortBy', 'dir', 'direction', 'sortDir', 'domain']);
@@ -109,9 +82,6 @@ export class QueryAdapter {
109
82
  return filters;
110
83
  }
111
84
 
112
- /**
113
- * Build a URLSearchParams from a params object, dropping empty values.
114
- */
115
85
  static build(params = {}) {
116
86
  const query = new URLSearchParams();
117
87
  Object.entries(params)
@@ -120,25 +90,16 @@ export class QueryAdapter {
120
90
  return query;
121
91
  }
122
92
 
123
- /**
124
- * Build a full URL from a base and params object.
125
- */
126
93
  static buildUrl(baseUrl, params = {}) {
127
94
  const queryString = QueryAdapter.build(params).toString();
128
95
  return queryString ? `${baseUrl}?${queryString}` : baseUrl;
129
96
  }
130
97
 
131
- /**
132
- * Get a default value for a config key (synchronous; delegates to getDefault).
133
- */
134
98
  static getDefault(key) {
135
99
  return getDefault(key);
136
100
  }
137
101
 
138
- /**
139
- * Parse pagination params from a URLSearchParams instance OR a plain object.
140
- * Synchronous so callers can destructure the result directly.
141
- */
102
+ // Synchronous so callers can destructure the result directly.
142
103
  static fromSearchParams(searchParams, spec = null) {
143
104
  const get = (key) => {
144
105
  if (searchParams && typeof searchParams.get === 'function') {
@@ -154,9 +115,6 @@ export class QueryAdapter {
154
115
  };
155
116
  }
156
117
 
157
- /**
158
- * Serialize a params object to a query string.
159
- */
160
118
  static toQueryString(params = {}) {
161
119
  return QueryAdapter.build(params).toString();
162
120
  }
@@ -1,30 +1,19 @@
1
- /**
2
- * Realtime Server - WebSocket/Server-Sent Events for live updates
3
- * Optional component - can be disabled if not needed
4
- */
1
+ import { createLogger } from './logger.js';
2
+
3
+ const log = createLogger('[Realtime]');
5
4
 
6
5
  let subscribers = new Map(); // channel -> Set of callbacks
7
6
  let enabled = false;
8
7
 
9
- /**
10
- * Initialize realtime (optional)
11
- * @param {object} server - HTTP server to attach WS endpoint
12
- */
13
8
  export function initRealtime(server) {
14
9
  if (!server) return;
15
10
 
16
11
  // Can be extended with ws library for full WebSocket support
17
12
  // For now, provides a polling-based notification system
18
13
  enabled = true;
19
- console.log('[Realtime] Initialized (polling mode)');
14
+ log.info('initialized (polling mode)');
20
15
  }
21
16
 
22
- /**
23
- * Broadcast update to all subscribers of a channel
24
- * @param {string} channel
25
- * @param {string} event
26
- * @param {any} data
27
- */
28
17
  export function broadcastUpdate(channel, event, data) {
29
18
  if (!enabled) return;
30
19
 
@@ -37,28 +26,17 @@ export function broadcastUpdate(channel, event, data) {
37
26
  }
38
27
  }
39
28
 
40
- /**
41
- * Subscribe to updates for a channel
42
- * @param {string} channel
43
- * @param {Function} callback
44
- * @returns {Function} Unsubscribe function
45
- */
46
29
  export function subscribe(channel, callback) {
47
30
  if (!subscribers.has(channel)) {
48
31
  subscribers.set(channel, new Set());
49
32
  }
50
33
  subscribers.get(channel).add(callback);
51
34
 
52
- // Return unsubscribe function
53
35
  return () => {
54
36
  subscribers.get(channel)?.delete(callback);
55
37
  };
56
38
  }
57
39
 
58
- /**
59
- * Get update stats
60
- * @returns {object}
61
- */
62
40
  export function getStats() {
63
41
  return {
64
42
  channels: subscribers.size,
@@ -1,8 +1,3 @@
1
- /**
2
- * Request Tracing Middleware - Instruments all HTTP requests with distributed tracing
3
- * Wraps handlers with automatic trace/span creation and context propagation
4
- */
5
-
6
1
  import { tracer } from './tracing.js';
7
2
  import { perfProfiler } from './perf-profiler.js';
8
3
  import { createLogger } from './logger.js';
@@ -1,6 +1,9 @@
1
1
  import os from 'os'
2
2
  import fs from 'fs'
3
3
  import { recordResource } from '@/lib/metrics-collector.js'
4
+ import { createLogger } from './logger.js';
5
+
6
+ const log = createLogger('[ResourceMonitor]');
4
7
 
5
8
  let monitoringInterval = null
6
9
  let dbPath = null
@@ -69,7 +72,7 @@ function collectResources() {
69
72
 
70
73
  return { cpu, memory, disk }
71
74
  } catch (err) {
72
- console.error('[ResourceMonitor] Collection error:', err)
75
+ log.error('collection error:', { message: err?.message || String(err) })
73
76
  return null
74
77
  }
75
78
  }
@@ -1,14 +1,5 @@
1
- /**
2
- * Response Formatter - Standardized API response helpers
3
- */
4
-
5
1
  import { HTTP } from '../config/constants.js';
6
2
 
7
- /**
8
- * Successful response with data
9
- * @param {any} data
10
- * @returns {Response} NextResponse-compatible
11
- */
12
3
  export function ok(data) {
13
4
  return new Response(JSON.stringify(data), {
14
5
  status: HTTP.OK,
@@ -16,11 +7,6 @@ export function ok(data) {
16
7
  });
17
8
  }
18
9
 
19
- /**
20
- * Created response (201)
21
- * @param {any} data
22
- * @returns {Response}
23
- */
24
10
  export function created(data) {
25
11
  return new Response(JSON.stringify(data), {
26
12
  status: HTTP.CREATED,
@@ -28,12 +14,6 @@ export function created(data) {
28
14
  });
29
15
  }
30
16
 
31
- /**
32
- * Paginated response
33
- * @param {Array} items
34
- * @param {object} pagination
35
- * @returns {Response}
36
- */
37
17
  export function paginated(items, pagination) {
38
18
  return new Response(JSON.stringify({
39
19
  items,
@@ -44,21 +24,10 @@ export function paginated(items, pagination) {
44
24
  });
45
25
  }
46
26
 
47
- /**
48
- * No content response (204)
49
- * @returns {Response}
50
- */
51
27
  export function noContent() {
52
28
  return new Response(null, { status: HTTP.NO_CONTENT });
53
29
  }
54
30
 
55
- /**
56
- * Error response
57
- * @param {string} message
58
- * @param {number} status
59
- * @param {string} code
60
- * @returns {Response}
61
- */
62
31
  export function error(message, status = HTTP.INTERNAL_ERROR, code = 'ERROR') {
63
32
  return new Response(JSON.stringify({ error: message, code }), {
64
33
  status,
@@ -66,13 +35,6 @@ export function error(message, status = HTTP.INTERNAL_ERROR, code = 'ERROR') {
66
35
  });
67
36
  }
68
37
 
69
- /**
70
- * With metadata wrapper
71
- * @param {any} data
72
- * @param {number} status
73
- * @param {string} type
74
- * @returns {object}
75
- */
76
38
  export function withMetadata(data, status, type = 'success') {
77
39
  return { ...data, _meta: { status, type, timestamp: Date.now() } };
78
40
  }
@@ -1,3 +1,4 @@
1
+ import { createLogger } from '@/lib/logger.js';
1
2
  import { getSpec, getNavItems } from '@/config/spec-helpers';
2
3
  import { get, getChildren } from '@/engine';
3
4
  import { can } from '@/services/permission.service';
@@ -25,7 +26,8 @@ export async function getFormContext(entityName, id = null, action = 'create') {
25
26
 
26
27
  export const getPageProps = (user, spec) => ({ user, nav: getNavItems(), canCreate: can(user, spec, 'create'), canEdit: can(user, spec, 'edit'), canDelete: can(user, spec, 'delete') });
27
28
 
28
- const createMeta = (fn) => (...args) => { try { return fn(...args); } catch { return { title: 'Not Found' }; } };
29
+ const log = createLogger('[RouteHelpers]');
30
+ const createMeta = (fn) => (...args) => { try { return fn(...args); } catch (err) { log.warn('metadata error:', { message: err?.message || String(err) }); return { title: 'Not Found' }; } };
29
31
 
30
32
  export const listMetadata = createMeta((entity) => ({ title: getSpec(entity).labelPlural }));
31
33
  export const detailMetadata = createMeta((entity, id) => { const spec = getSpec(entity), data = get(entity, id); return { title: data?.name || data?.email || spec.label }; });
@@ -1,8 +1,12 @@
1
+ import { createLogger } from './logger.js';
2
+
3
+ const log = createLogger('[SafeJSON]');
4
+
1
5
  export const safeJsonParse = (str, fallback = null) => {
2
6
  try {
3
7
  return JSON.parse(str || 'null') ?? fallback;
4
8
  } catch {
5
- console.warn('[SafeJSON] Parse failed:', str?.substring(0, 50));
9
+ log.warn('parse failed:', { preview: str?.substring(0, 50) });
6
10
  return fallback;
7
11
  }
8
12
  };
@@ -1,5 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { createLogger } from './logger.js';
4
+
5
+ const log = createLogger('[Server]');
3
6
 
4
7
  export function loadEnv(filePath) {
5
8
  try {
@@ -12,18 +15,16 @@ export function loadEnv(filePath) {
12
15
  if (key) process.env[key] = value;
13
16
  });
14
17
  } catch (e) {
15
- console.warn('[Server] .env file not loaded:', e.message);
18
+ log.warn('.env file not loaded:', { message: e.message });
16
19
  }
17
20
  }
18
21
 
19
22
  export function setupProcessGuards() {
20
23
  process.on('uncaughtException', (err) => {
21
- console.error('[FATAL] Uncaught exception (process kept alive):', err?.message || err);
22
- if (err?.stack) console.error(err.stack);
24
+ log.error('uncaught exception (process kept alive):', { message: err?.message || String(err), stack: err?.stack });
23
25
  });
24
26
  process.on('unhandledRejection', (reason) => {
25
- console.error('[FATAL] Unhandled rejection (process kept alive):', reason?.message || reason);
26
- if (reason?.stack) console.error(reason.stack);
27
+ log.error('unhandled rejection (process kept alive):', { message: reason?.message || String(reason), stack: reason?.stack });
27
28
  });
28
29
  }
29
30
 
@@ -36,18 +37,18 @@ export function setupHotReload(__dirname, moduleCache, onConfigReset) {
36
37
  if (filename && (filename.endsWith('.js') || filename.endsWith('.jsx') || filename.endsWith('.yml'))) {
37
38
  moduleCache.clear();
38
39
  globalThis.__reloadTs__ = Date.now();
39
- console.log(`[Hot] Invalidated: ${filename}`);
40
+ log.info(`hot invalidated: ${filename}`);
40
41
  if (filename.endsWith('master-config.yml') || filename.includes('master-config')) {
41
42
  try {
42
43
  const { resetConfigEngine } = await import('./config-generator-engine.js');
43
44
  resetConfigEngine();
44
45
  onConfigReset?.();
45
- } catch (e) { console.log('[Hot] Could not reset config engine:', e.message); }
46
+ } catch (e) { log.warn('could not reset config engine:', { message: e.message }); }
46
47
  }
47
48
  }
48
49
  });
49
- watcher.on('error', (err) => console.error(`[Hot] Watcher error on ${dir}:`, err.message));
50
- } catch (err) { console.error(`[Hot] Failed to watch ${dir}:`, err.message); }
50
+ watcher.on('error', (err) => log.error(`hot watcher error on ${dir}:`, { message: err.message }));
51
+ } catch (err) { log.error(`hot failed to watch ${dir}:`, { message: err.message }); }
51
52
  });
52
53
  }
53
54
 
@@ -52,7 +52,7 @@ export function createStagePipeline(options = {}) {
52
52
  `;
53
53
 
54
54
  if (isComplete) {
55
- circle.textContent = '✓';
55
+ circle.textContent = 'ok';
56
56
  } else {
57
57
  circle.textContent = idx + 1;
58
58
  }
@@ -110,7 +110,7 @@ export function createStagePipeline(options = {}) {
110
110
 
111
111
  if (i < idx) {
112
112
  circle.style.background = '#28a745';
113
- circle.textContent = '✓';
113
+ circle.textContent = 'ok';
114
114
  label.style.color = '#28a745';
115
115
  } else if (i === idx) {
116
116
  circle.style.background = '#007bff';
@@ -1,5 +1,8 @@
1
1
  import Protocol from './state-protocol.js'
2
2
  import { CONFIG, ReconnectManager } from './state-transport-reconnect.js'
3
+ import { createLogger } from './logger.js'
4
+
5
+ const log = createLogger('[StateTransport]')
3
6
 
4
7
  class StateTransportClient {
5
8
  constructor(config = {}) {
@@ -151,7 +154,7 @@ class StateTransportClient {
151
154
  try {
152
155
  handler(data)
153
156
  } catch (error) {
154
- console.error(`Error in event handler for ${event}:`, error)
157
+ log.error(`event handler error for ${event}:`, { message: error?.message || String(error) })
155
158
  }
156
159
  }
157
160
  }