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.
- package/README.md +4 -16
- package/package.json +12 -18
- package/src/adapters/google-auth.js +4 -43
- package/src/adapters/google-drive.js +12 -67
- package/src/app/api/audit/dashboard/route.js +14 -11
- package/src/app/api/audit/logs/route.js +2 -2
- package/src/app/api/audit/permissions/[id]/route.js +6 -3
- package/src/app/api/audit/permissions/route.js +10 -7
- package/src/app/api/audit/permissions/stats/route.js +8 -5
- package/src/app/api/audit/route.js +4 -4
- package/src/app/api/audit/stats/route.js +1 -1
- package/src/app/api/auth/google/callback/route.js +5 -2
- package/src/app/api/auth/google/route.js +4 -1
- package/src/app/api/auth/login/route.js +5 -2
- package/src/app/api/auth/mwr-bridge/route.js +11 -21
- package/src/app/api/auth/password-reset/route.js +15 -14
- package/src/app/api/cron/trigger/route.js +4 -1
- package/src/app/api/debug/[[...path]]/route.js +6 -5
- package/src/app/api/debug/sqlite/route.js +7 -12
- package/src/app/api/debug/sync/route.js +5 -5
- package/src/app/api/domains/route.js +4 -1
- package/src/app/api/email/allocate/batch/route.js +19 -29
- package/src/app/api/email/allocate/route.js +16 -17
- package/src/app/api/email/receive/route.js +31 -42
- package/src/app/api/email/send/route.js +28 -16
- package/src/app/api/email/unallocated/route.js +10 -13
- package/src/app/api/files/[id]/route.js +4 -1
- package/src/app/api/formula/[[...path]]/route.js +0 -4
- package/src/app/api/health/route.js +10 -9
- package/src/app/api/metrics/route.js +1 -1
- package/src/cli.js +4 -4
- package/src/config/config-loader.js +1 -21
- package/src/config/constants.js +0 -35
- package/src/config/env.js +0 -30
- package/src/config/spec-helpers.js +6 -95
- package/src/engine.js +23 -0
- package/src/engine.server.js +8 -78
- package/src/index.js +42 -67
- package/src/lib/alert-manager.js +7 -3
- package/src/lib/api-helpers.js +4 -3
- package/src/lib/api.js +10 -10
- package/src/lib/audit-logger-enhanced.js +77 -83
- package/src/lib/auth-middleware.js +1 -32
- package/src/lib/auth-route-helpers.js +5 -0
- package/src/lib/busybase-adapter.js +0 -5
- package/src/lib/busybase-audit-reads.js +142 -0
- package/src/lib/busybase-audit.js +41 -0
- package/src/lib/busybase-lucia-adapter.js +72 -0
- package/src/lib/busybase-store.js +338 -0
- package/src/lib/config-field-helpers.js +0 -17
- package/src/lib/config-generator-engine.js +19 -36
- package/src/lib/config-helpers.js +0 -21
- package/src/lib/crud-action-helpers.js +2 -2
- package/src/lib/crud-factory.js +1 -16
- package/src/lib/crud-handlers.js +17 -52
- package/src/lib/csrf-protection.js +5 -2
- package/src/lib/date-utils.js +3 -127
- package/src/lib/debug-registry.js +0 -5
- package/src/lib/domain-loader.js +4 -1
- package/src/lib/email-sender.js +25 -23
- package/src/lib/error-handler.js +5 -41
- package/src/lib/error-recovery.js +10 -7
- package/src/lib/error-resilience.js +7 -5
- package/src/lib/events-engine.js +9 -6
- package/src/lib/export-sink.js +0 -5
- package/src/lib/field-iterator.js +7 -76
- package/src/lib/field-registry.js +0 -22
- package/src/lib/generic-crud-handler.js +0 -16
- package/src/lib/hook-engine.js +5 -53
- package/src/lib/hot-reload/checkpoint.js +5 -2
- package/src/lib/hot-reload/promise-container.js +7 -3
- package/src/lib/hot-reload/route-wrapper.js +4 -1
- package/src/lib/hot-reload/supervisor.js +6 -3
- package/src/lib/hyperformula-service.js +0 -5
- package/src/lib/id-helpers.js +10 -0
- package/src/lib/index.js +4 -7
- package/src/lib/log-aggregator.js +0 -1
- package/src/lib/logger.js +0 -15
- package/src/lib/metrics-collector.js +2 -46
- package/src/lib/next-compat.js +15 -1
- package/src/lib/observability-bootstrap.js +0 -5
- package/src/lib/perf-monitor.js +4 -1
- package/src/lib/perf-profiler.js +0 -5
- package/src/lib/query-string-adapter.js +5 -47
- package/src/lib/realtime-server.js +4 -26
- package/src/lib/request-tracing.js +0 -5
- package/src/lib/resource-monitor.js +4 -1
- package/src/lib/response-formatter.js +0 -38
- package/src/lib/route-helpers.js +3 -1
- package/src/lib/safe-json.js +5 -1
- package/src/lib/server-bootstrap.js +10 -9
- package/src/lib/stage-pipeline.js +2 -2
- package/src/lib/state-transport-client.js +4 -1
- package/src/lib/static-server.js +2 -2
- package/src/lib/status-helpers.js +2 -51
- package/src/lib/tracing.js +0 -5
- package/src/lib/utils.js +6 -67
- package/src/lib/validate.js +5 -88
- package/src/lib/validators.js +0 -38
- package/src/lib/with-audit-logging.js +1 -1
- package/src/lib/workflow-engine.js +10 -77
- package/src/lib/xstate-workflow-engine.js +3 -9
- package/src/plugins/index.js +5 -3
- package/src/server/server.js +26 -27
- package/src/services/collaborator-role.service.js +21 -77
- package/src/services/email-sender.js +7 -34
- package/src/services/notification-engine.js +12 -33
- package/src/services/permission.service.js +10 -73
- package/src/ui/advanced-search-renderer.js +3 -2
- package/src/ui/advanced-widgets.js +1 -1
- package/src/ui/auth-pages.js +10 -9
- package/src/ui/auth-styles.js +1 -1
- package/src/ui/client.js +1 -1
- package/src/ui/component-engine.js +1 -1
- package/src/ui/dashboard-renderer.js +5 -5
- package/src/ui/dialog-engine.js +6 -9
- package/src/ui/dialog-factory.js +2 -2
- package/src/ui/engagement-cards.js +7 -7
- package/src/ui/engagement-dialogs.js +2 -3
- package/src/ui/engagement-grid-renderer.js +20 -19
- package/src/ui/entity-renderer.js +56 -42
- package/src/ui/file-dialogs.js +25 -9
- package/src/ui/flexup-report-renderer.js +21 -18
- package/src/ui/format-helpers.js +27 -28
- package/src/ui/highlight-threading-renderer.js +9 -9
- package/src/ui/job-management-renderer.js +18 -16
- package/src/ui/layout.js +51 -25
- package/src/ui/ml-console-renderer.js +15 -16
- package/src/ui/monitoring-dashboard-client.js +9 -8
- package/src/ui/notifications-renderer.js +11 -10
- package/src/ui/page-handler-admin.js +32 -23
- package/src/ui/page-handler-helpers.js +27 -23
- package/src/ui/page-handler-reviews.js +71 -35
- package/src/ui/page-handler-rfi.js +28 -14
- package/src/ui/page-handler.js +65 -48
- package/src/ui/picker-dialogs.js +26 -19
- package/src/ui/render-helpers.js +84 -46
- package/src/ui/review-comparison-renderer.js +8 -7
- package/src/ui/review-detail-panels.js +8 -9
- package/src/ui/review-detail-renderer.js +3 -3
- package/src/ui/review-detail-script.js +13 -12
- package/src/ui/review-mwr-renderer.js +6 -5
- package/src/ui/review-renderer.js +7 -6
- package/src/ui/review-widgets.js +3 -3
- package/src/ui/rfi-detail-renderer.js +37 -20
- package/src/ui/rfi-report-renderer.js +14 -9
- package/src/ui/settings-renderer-advanced.js +21 -18
- package/src/ui/settings-renderer-advanced2.js +20 -19
- package/src/ui/settings-renderer-teams.js +4 -4
- package/src/ui/settings-renderer.js +4 -4
- package/src/ui/spacing-system.js +5 -2
- package/src/ui/standalone-login.js +2 -2
- package/src/ui/styles.css +2 -1
- package/src/ui/styles2.css +75 -5
- package/src/ui/test-page.js +4 -4
- package/src/ui/widgets.js +20 -15
- package/src/lib/audit-logger.js +0 -193
- package/src/lib/database-core.js +0 -255
- package/src/lib/database-migrations.js +0 -350
- package/src/lib/db-backup.js +0 -97
- package/src/lib/error-boundary.js +0 -134
- package/src/lib/errors.js +0 -69
- package/src/lib/health-monitor.js +0 -134
- package/src/lib/index-optimizer.js +0 -96
- package/src/lib/query-engine-write.js +0 -221
- package/src/lib/query-engine.js +0 -446
- package/src/ui/collaboration-dialogs.js +0 -31
- package/src/ui/global-tags.js +0 -144
- package/src/ui/idle-logout.js +0 -156
- package/src/ui/letter-dialogs.js +0 -37
- package/src/ui/password-reset-page.js +0 -146
- package/src/ui/perf-renderer.js +0 -71
- package/src/ui/rfi-renderer.js +0 -194
- package/src/ui/validation-ui.js +0 -147
package/src/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from '@/lib/
|
|
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
|
|
29
|
-
export {
|
|
30
|
-
export { logAction
|
|
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';
|
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);
|
|
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();
|
package/src/lib/next-compat.js
CHANGED
|
@@ -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';
|
package/src/lib/perf-monitor.js
CHANGED
|
@@ -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
|
-
|
|
55
|
+
log.warn(`${key} exceeded threshold: ${value.toFixed(2)}ms > ${limit}ms`)
|
|
53
56
|
return false
|
|
54
57
|
}
|
|
55
58
|
return true
|
package/src/lib/perf-profiler.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/lib/route-helpers.js
CHANGED
|
@@ -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
|
|
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 }; });
|
package/src/lib/safe-json.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
46
|
+
} catch (e) { log.warn('could not reset config engine:', { message: e.message }); }
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
});
|
|
49
|
-
watcher.on('error', (err) =>
|
|
50
|
-
} catch (err) {
|
|
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
|
-
|
|
157
|
+
log.error(`event handler error for ${event}:`, { message: error?.message || String(error) })
|
|
155
158
|
}
|
|
156
159
|
}
|
|
157
160
|
}
|