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/engine.server.js
CHANGED
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
* Auth Engine - Lucia session management and Google OAuth
|
|
3
|
-
* Simplified extraction from moonlanding/src/engine.server.js
|
|
4
|
-
*/
|
|
5
|
-
|
|
1
|
+
// Simplified extraction from moonlanding/src/engine.server.js
|
|
6
2
|
import { Lucia } from 'lucia';
|
|
7
|
-
import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite';
|
|
8
3
|
import { Google } from 'arctic';
|
|
9
4
|
import bcrypt from 'bcrypt';
|
|
10
|
-
import {
|
|
5
|
+
import { BusyBaseLuciaAdapter } from './lib/busybase-lucia-adapter.js';
|
|
11
6
|
import { buildConfig, hasGoogleAuth } from './config/env.js';
|
|
12
7
|
|
|
13
8
|
let _lucia = null;
|
|
14
9
|
let _google = null;
|
|
15
10
|
let _adapter = null;
|
|
16
11
|
|
|
17
|
-
/**
|
|
18
|
-
* Initialize auth engine with database and config
|
|
19
|
-
* @param {object} [config] - Optional config override
|
|
20
|
-
*/
|
|
21
12
|
export function initAuth(config = null) {
|
|
22
13
|
const cfg = config || buildConfig();
|
|
23
|
-
const db = getDatabase(cfg.db.path);
|
|
24
14
|
|
|
25
|
-
_adapter = new
|
|
15
|
+
_adapter = new BusyBaseLuciaAdapter();
|
|
26
16
|
|
|
27
17
|
_lucia = new Lucia(_adapter, {
|
|
28
18
|
sessionCookie: {
|
|
@@ -54,29 +44,16 @@ export function initAuth(config = null) {
|
|
|
54
44
|
return { lucia: _lucia, google: _google };
|
|
55
45
|
}
|
|
56
46
|
|
|
57
|
-
/**
|
|
58
|
-
* Get Lucia instance
|
|
59
|
-
* @returns {Lucia}
|
|
60
|
-
*/
|
|
61
47
|
export function getLucia() {
|
|
62
48
|
if (!_lucia) initAuth();
|
|
63
49
|
return _lucia;
|
|
64
50
|
}
|
|
65
51
|
|
|
66
|
-
/**
|
|
67
|
-
* Get Google OAuth client
|
|
68
|
-
* @returns {Google|null}
|
|
69
|
-
*/
|
|
70
52
|
export function getGoogle() {
|
|
71
53
|
if (!_google) initAuth();
|
|
72
54
|
return _google;
|
|
73
55
|
}
|
|
74
56
|
|
|
75
|
-
/**
|
|
76
|
-
* Parse cookies from header
|
|
77
|
-
* @param {string} cookieHeader
|
|
78
|
-
* @returns {object}
|
|
79
|
-
*/
|
|
80
57
|
function parseCookies(cookieHeader) {
|
|
81
58
|
const cookies = {};
|
|
82
59
|
if (!cookieHeader) return cookies;
|
|
@@ -89,18 +66,10 @@ function parseCookies(cookieHeader) {
|
|
|
89
66
|
|
|
90
67
|
let _currentRequest = null;
|
|
91
68
|
|
|
92
|
-
/**
|
|
93
|
-
* Set current request context (for auth)
|
|
94
|
-
* @param {object} req
|
|
95
|
-
*/
|
|
96
69
|
export function setCurrentRequest(req) {
|
|
97
70
|
_currentRequest = req;
|
|
98
71
|
}
|
|
99
72
|
|
|
100
|
-
/**
|
|
101
|
-
* Get current user from session
|
|
102
|
-
* @returns {object|null}
|
|
103
|
-
*/
|
|
104
73
|
export async function getUser() {
|
|
105
74
|
try {
|
|
106
75
|
const request = _currentRequest;
|
|
@@ -120,21 +89,12 @@ export async function getUser() {
|
|
|
120
89
|
}
|
|
121
90
|
}
|
|
122
91
|
|
|
123
|
-
/**
|
|
124
|
-
* Require user (throw if not authenticated)
|
|
125
|
-
* @returns {Promise<object>}
|
|
126
|
-
*/
|
|
127
92
|
export async function requireUser() {
|
|
128
93
|
const user = await getUser();
|
|
129
94
|
if (!user) throw new Error('Unauthorized');
|
|
130
95
|
return user;
|
|
131
96
|
}
|
|
132
97
|
|
|
133
|
-
/**
|
|
134
|
-
* Create a new session
|
|
135
|
-
* @param {string} userId
|
|
136
|
-
* @returns {{session: object, sessionCookie: object}}
|
|
137
|
-
*/
|
|
138
98
|
export async function createSession(userId) {
|
|
139
99
|
const lucia = getLucia();
|
|
140
100
|
// Note: cookies() requires Next.js polyfill in moonlanding; for SDK we return session data
|
|
@@ -143,66 +103,36 @@ export async function createSession(userId) {
|
|
|
143
103
|
return { session, sessionCookie };
|
|
144
104
|
}
|
|
145
105
|
|
|
146
|
-
/**
|
|
147
|
-
* Invalidate current session
|
|
148
|
-
*/
|
|
149
106
|
export async function invalidateSession() {
|
|
150
107
|
const lucia = getLucia();
|
|
151
108
|
// Simplified - caller handles cookie clearing
|
|
152
109
|
// In full app this uses @lucia-auth/adapter-sqlite's cookie management
|
|
153
110
|
}
|
|
154
111
|
|
|
155
|
-
/**
|
|
156
|
-
* Hash password with bcrypt
|
|
157
|
-
* @param {string} password
|
|
158
|
-
* @returns {Promise<string>}
|
|
159
|
-
*/
|
|
160
112
|
export async function hashPassword(password) {
|
|
161
113
|
return bcrypt.hash(password, 12);
|
|
162
114
|
}
|
|
163
115
|
|
|
164
|
-
/**
|
|
165
|
-
* Verify password
|
|
166
|
-
* @param {string} password
|
|
167
|
-
* @param {string} hash
|
|
168
|
-
* @returns {Promise<boolean>}
|
|
169
|
-
*/
|
|
170
116
|
export async function verifyPassword(password, hash) {
|
|
171
117
|
return bcrypt.compare(password, hash);
|
|
172
118
|
}
|
|
173
119
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
* @returns {object|null}
|
|
178
|
-
*/
|
|
179
|
-
export function getUserByEmail(email) {
|
|
180
|
-
const db = getDatabase();
|
|
181
|
-
return db.prepare('SELECT * FROM users WHERE email = ?').get(email);
|
|
120
|
+
export async function getUserByEmail(email) {
|
|
121
|
+
const { getBy } = await import('./lib/busybase-store.js');
|
|
122
|
+
return getBy('user', 'email', email);
|
|
182
123
|
}
|
|
183
124
|
|
|
184
|
-
/**
|
|
185
|
-
* Create a new user
|
|
186
|
-
* @param {object} userData
|
|
187
|
-
* @returns {object}
|
|
188
|
-
*/
|
|
189
125
|
export async function createUser(userData) {
|
|
190
126
|
const hashedPassword = await hashPassword(userData.password);
|
|
191
|
-
const { create } = await import('./
|
|
127
|
+
const { create } = await import('./lib/busybase-store.js');
|
|
192
128
|
return create('user', {
|
|
193
129
|
...userData,
|
|
194
130
|
password: hashedPassword,
|
|
195
131
|
}, { id: 'system' });
|
|
196
132
|
}
|
|
197
133
|
|
|
198
|
-
/**
|
|
199
|
-
* Authenticate user with credentials
|
|
200
|
-
* @param {string} email
|
|
201
|
-
* @param {string} password
|
|
202
|
-
* @returns {object|null} User if valid, null otherwise
|
|
203
|
-
*/
|
|
204
134
|
export async function authenticate(email, password) {
|
|
205
|
-
const user = getUserByEmail(email);
|
|
135
|
+
const user = await getUserByEmail(email);
|
|
206
136
|
if (!user) return null;
|
|
207
137
|
|
|
208
138
|
const valid = await verifyPassword(password, user.password);
|
package/src/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thatcher SDK - Configuration-Driven Application Framework
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import fs from 'fs';
|
|
6
2
|
import path from 'path';
|
|
7
3
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
4
|
+
import { createLogger } from './lib/logger.js';
|
|
8
5
|
|
|
9
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const log = createLogger('[Thatcher]');
|
|
10
8
|
|
|
11
9
|
// Forward-declare so we can set later
|
|
12
10
|
let _configEngine = null;
|
|
@@ -15,19 +13,18 @@ let _pluginsLoaded = false;
|
|
|
15
13
|
let _server = null;
|
|
16
14
|
let _hotReloadWatchers = [];
|
|
17
15
|
|
|
18
|
-
/**
|
|
19
|
-
* Resolve a path relative to this package's src directory as an import URL
|
|
20
|
-
* @param {string} relative - Path like './src/lib/database-core.js'
|
|
21
|
-
* @returns {string} file:// URL
|
|
22
|
-
*/
|
|
23
16
|
function resolveModule(relative) {
|
|
24
17
|
const abs = path.resolve(__dirname, relative);
|
|
25
18
|
return pathToFileURL(abs).href;
|
|
26
19
|
}
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
// busybase stores data in a directory. Callers pass `databasePath` which may be
|
|
22
|
+
// either a directory or a path ending in a db filename (.db/.sqlite); reduce the
|
|
23
|
+
// latter to its containing directory.
|
|
24
|
+
function databasePathToDir(p) {
|
|
25
|
+
return /\.(db|sqlite|sqlite3)$/i.test(p) ? path.dirname(p) : p;
|
|
26
|
+
}
|
|
27
|
+
|
|
31
28
|
export class Thatcher {
|
|
32
29
|
constructor(options = {}) {
|
|
33
30
|
this.options = this.normalizeOptions(options);
|
|
@@ -51,9 +48,6 @@ export class Thatcher {
|
|
|
51
48
|
};
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
/**
|
|
55
|
-
* Initialize the system (config, DB, plugins)
|
|
56
|
-
*/
|
|
57
51
|
async init() {
|
|
58
52
|
if (this.initialized) return this;
|
|
59
53
|
|
|
@@ -78,9 +72,6 @@ export class Thatcher {
|
|
|
78
72
|
return this;
|
|
79
73
|
}
|
|
80
74
|
|
|
81
|
-
/**
|
|
82
|
-
* Load master configuration
|
|
83
|
-
*/
|
|
84
75
|
async loadConfig() {
|
|
85
76
|
const { config } = this.options;
|
|
86
77
|
let masterConfig;
|
|
@@ -138,21 +129,31 @@ export class Thatcher {
|
|
|
138
129
|
this.config = masterConfig;
|
|
139
130
|
}
|
|
140
131
|
|
|
141
|
-
/**
|
|
142
|
-
* Initialize database with schema from config
|
|
143
|
-
*/
|
|
144
132
|
async initDatabase() {
|
|
145
133
|
if (_databaseInitialized) return;
|
|
146
|
-
const {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
134
|
+
const { createEmbedded } = await import('busybase/embedded');
|
|
135
|
+
// Honour the caller's chosen location. `databasePath` is the documented
|
|
136
|
+
// option; busybase wants a directory, so a value that looks like a db file
|
|
137
|
+
// is reduced to its dirname. `dataDir` and BUSYBASE_DIR remain fallbacks.
|
|
138
|
+
const dir = this.options.dataDir
|
|
139
|
+
|| (this.options.databasePath ? databasePathToDir(this.options.databasePath) : null)
|
|
140
|
+
|| process.env.BUSYBASE_DIR
|
|
141
|
+
|| 'busybase_data';
|
|
142
|
+
const client = await createEmbedded({ dir });
|
|
143
|
+
|
|
144
|
+
const store = await import(resolveModule('./lib/busybase-store.js'));
|
|
145
|
+
store.setBusyBaseClient(client);
|
|
146
|
+
const audit = await import(resolveModule('./lib/busybase-audit.js'));
|
|
147
|
+
audit.setBusyBaseClient(client);
|
|
148
|
+
|
|
149
|
+
this.busybase = client;
|
|
150
|
+
globalThis.__thatcherBusyBase = client;
|
|
151
|
+
if (globalThis.__debug__) {
|
|
152
|
+
globalThis.__debug__.expose('datastore', { kind: () => 'busybase', dir: () => dir }, 'Data Store');
|
|
153
|
+
}
|
|
150
154
|
_databaseInitialized = true;
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
/**
|
|
154
|
-
* Load .plugin.js files
|
|
155
|
-
*/
|
|
156
157
|
async loadPlugins() {
|
|
157
158
|
if (_pluginsLoaded) return;
|
|
158
159
|
|
|
@@ -179,7 +180,7 @@ export class Thatcher {
|
|
|
179
180
|
_configEngine.registerPlugin(plugin.entityName, plugin);
|
|
180
181
|
}
|
|
181
182
|
} catch (e) {
|
|
182
|
-
|
|
183
|
+
log.error(`plugins: failed to load ${file}: ${e.message}`);
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
}
|
|
@@ -188,9 +189,6 @@ export class Thatcher {
|
|
|
188
189
|
_pluginsLoaded = true;
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
/**
|
|
192
|
-
* Setup hot reload watchers
|
|
193
|
-
*/
|
|
194
192
|
setupHotReload() {
|
|
195
193
|
const dirs = [
|
|
196
194
|
path.resolve(process.cwd(), 'config'),
|
|
@@ -204,13 +202,13 @@ export class Thatcher {
|
|
|
204
202
|
try {
|
|
205
203
|
const watcher = fs.watch(dir, { recursive: true }, (_, filename) => {
|
|
206
204
|
if (filename && (filename.endsWith('.js') || filename.endsWith('.jsx') || filename.endsWith('.yml'))) {
|
|
207
|
-
|
|
205
|
+
log.info(`hot reload: ${filename} changed`);
|
|
208
206
|
this.invalidateCache();
|
|
209
207
|
}
|
|
210
208
|
});
|
|
211
209
|
_hotReloadWatchers.push(watcher);
|
|
212
210
|
} catch (err) {
|
|
213
|
-
|
|
211
|
+
log.warn(`hot reload: failed to watch ${dir}: ${err.message}`);
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
214
|
}
|
|
@@ -221,9 +219,6 @@ export class Thatcher {
|
|
|
221
219
|
}
|
|
222
220
|
}
|
|
223
221
|
|
|
224
|
-
/**
|
|
225
|
-
* Start HTTP server
|
|
226
|
-
*/
|
|
227
222
|
async startServer(opts = {}) {
|
|
228
223
|
if (!this.initialized) throw new Error('Call init() first');
|
|
229
224
|
|
|
@@ -241,15 +236,12 @@ export class Thatcher {
|
|
|
241
236
|
|
|
242
237
|
return new Promise((resolve) => {
|
|
243
238
|
_server.listen(port, host, () => {
|
|
244
|
-
console.log(`\n
|
|
239
|
+
console.log(`\n> Thatcher Server\n- Local: http://localhost:${port}\n[OK] Ready\n`);
|
|
245
240
|
resolve(_server);
|
|
246
241
|
});
|
|
247
242
|
});
|
|
248
243
|
}
|
|
249
244
|
|
|
250
|
-
/**
|
|
251
|
-
* Stop server
|
|
252
|
-
*/
|
|
253
245
|
async stop() {
|
|
254
246
|
if (_server) {
|
|
255
247
|
_server.close();
|
|
@@ -259,30 +251,18 @@ export class Thatcher {
|
|
|
259
251
|
_hotReloadWatchers = [];
|
|
260
252
|
}
|
|
261
253
|
|
|
262
|
-
/**
|
|
263
|
-
* Get config engine
|
|
264
|
-
*/
|
|
265
254
|
getConfigEngine() {
|
|
266
255
|
return _configEngine;
|
|
267
256
|
}
|
|
268
257
|
|
|
269
|
-
/**
|
|
270
|
-
* Get entity spec
|
|
271
|
-
*/
|
|
272
258
|
getEntitySpec(name) {
|
|
273
259
|
return _configEngine?.generateEntitySpec(name);
|
|
274
260
|
}
|
|
275
261
|
|
|
276
|
-
/**
|
|
277
|
-
* List all entities
|
|
278
|
-
*/
|
|
279
262
|
getAllEntities() {
|
|
280
263
|
return _configEngine?.getAllEntities() || [];
|
|
281
264
|
}
|
|
282
265
|
|
|
283
|
-
/**
|
|
284
|
-
* Get workflow definition
|
|
285
|
-
*/
|
|
286
266
|
getWorkflow(name) {
|
|
287
267
|
return _configEngine?.getWorkflow(name);
|
|
288
268
|
}
|
|
@@ -290,32 +270,32 @@ export class Thatcher {
|
|
|
290
270
|
// === CRUD operations ===
|
|
291
271
|
|
|
292
272
|
async list(entity, where = {}, opts = {}) {
|
|
293
|
-
const { list } = await import(resolveModule('./lib/
|
|
273
|
+
const { list } = await import(resolveModule('./lib/busybase-store.js'));
|
|
294
274
|
return list(entity, where, opts);
|
|
295
275
|
}
|
|
296
276
|
|
|
297
277
|
async get(entity, id) {
|
|
298
|
-
const { get } = await import(resolveModule('./lib/
|
|
278
|
+
const { get } = await import(resolveModule('./lib/busybase-store.js'));
|
|
299
279
|
return get(entity, id);
|
|
300
280
|
}
|
|
301
281
|
|
|
302
282
|
async create(entity, data, user) {
|
|
303
|
-
const { create } = await import(resolveModule('./lib/
|
|
283
|
+
const { create } = await import(resolveModule('./lib/busybase-store.js'));
|
|
304
284
|
return create(entity, data, user);
|
|
305
285
|
}
|
|
306
286
|
|
|
307
287
|
async update(entity, id, data, user) {
|
|
308
|
-
const { update } = await import(resolveModule('./lib/
|
|
288
|
+
const { update } = await import(resolveModule('./lib/busybase-store.js'));
|
|
309
289
|
return update(entity, id, data, user);
|
|
310
290
|
}
|
|
311
291
|
|
|
312
292
|
async delete(entity, id) {
|
|
313
|
-
const { remove } = await import(resolveModule('./lib/
|
|
293
|
+
const { remove } = await import(resolveModule('./lib/busybase-store.js'));
|
|
314
294
|
return remove(entity, id);
|
|
315
295
|
}
|
|
316
296
|
|
|
317
297
|
async search(entity, query, where = {}, opts = {}) {
|
|
318
|
-
const { search } = await import(resolveModule('./lib/
|
|
298
|
+
const { search } = await import(resolveModule('./lib/busybase-store.js'));
|
|
319
299
|
return search(entity, query, where, opts);
|
|
320
300
|
}
|
|
321
301
|
|
|
@@ -353,21 +333,16 @@ export class Thatcher {
|
|
|
353
333
|
// === Database transaction ===
|
|
354
334
|
|
|
355
335
|
async withTransaction(callback) {
|
|
356
|
-
|
|
357
|
-
|
|
336
|
+
// busybase has no transaction primitive; the old query-engine's withTransaction
|
|
337
|
+
// was a pass-through, so preserve that contract rather than import a dead file.
|
|
338
|
+
return callback();
|
|
358
339
|
}
|
|
359
340
|
}
|
|
360
341
|
|
|
361
|
-
/**
|
|
362
|
-
* Create thatcher instance
|
|
363
|
-
*/
|
|
364
342
|
export function createThatcher(options) {
|
|
365
343
|
return new Thatcher(options);
|
|
366
344
|
}
|
|
367
345
|
|
|
368
|
-
/**
|
|
369
|
-
* Quick-start helper
|
|
370
|
-
*/
|
|
371
346
|
export async function startThatcher(options = {}) {
|
|
372
347
|
const t = new Thatcher(options);
|
|
373
348
|
await t.init();
|
package/src/lib/alert-manager.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { createLogger } from './logger.js';
|
|
2
|
+
|
|
3
|
+
const log = createLogger('[AlertManager]');
|
|
4
|
+
|
|
1
5
|
const alerts = []
|
|
2
6
|
const alertHandlers = []
|
|
3
7
|
const thresholds = {
|
|
@@ -105,13 +109,13 @@ function triggerAlert(type, message, metadata = {}) {
|
|
|
105
109
|
alerts.push(alert)
|
|
106
110
|
if (alerts.length > 1000) alerts.shift()
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
log.error(`[${alert.severity.toUpperCase()}] ${type}: ${message}`, metadata)
|
|
109
113
|
|
|
110
114
|
for (const handler of alertHandlers) {
|
|
111
115
|
try {
|
|
112
116
|
handler(alert)
|
|
113
117
|
} catch (err) {
|
|
114
|
-
|
|
118
|
+
log.error('handler error:', { message: err.message })
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
}
|
|
@@ -133,7 +137,7 @@ function checkAllThresholds(allMetrics) {
|
|
|
133
137
|
checkResources(allMetrics.resources)
|
|
134
138
|
checkDatabase(allMetrics.database)
|
|
135
139
|
} catch (err) {
|
|
136
|
-
|
|
140
|
+
log.error('check error:', { message: err.message })
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
143
|
|
package/src/lib/api-helpers.js
CHANGED
|
@@ -4,16 +4,17 @@ import { getSpec } from '@/config/spec-helpers';
|
|
|
4
4
|
import { migrate } from '@/engine';
|
|
5
5
|
import { getUser } from '@/engine.server';
|
|
6
6
|
import { can } from '@/services/permission.service';
|
|
7
|
-
import {
|
|
7
|
+
import { createLogger } from '@/lib/logger';
|
|
8
8
|
import { HTTP } from '@/config/constants';
|
|
9
9
|
import { ERROR_MESSAGES } from '@/config';
|
|
10
10
|
|
|
11
|
+
const log = createLogger('[API]');
|
|
12
|
+
|
|
11
13
|
let dbInit = false;
|
|
12
14
|
export function ensureDb() {
|
|
13
15
|
if (!dbInit) {
|
|
14
16
|
migrate();
|
|
15
17
|
dbInit = true;
|
|
16
|
-
} else {
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -41,7 +42,7 @@ export async function withEntityAccess(entity, action, handler) {
|
|
|
41
42
|
if (!can(user, spec, action)) return unauthorized(action);
|
|
42
43
|
return await handler(spec, user);
|
|
43
44
|
} catch (e) {
|
|
44
|
-
|
|
45
|
+
log.error(`${action} ${entity}: ${e.message}`);
|
|
45
46
|
return serverError(e.message);
|
|
46
47
|
}
|
|
47
48
|
}
|
package/src/lib/api.js
CHANGED
|
@@ -2,7 +2,7 @@ import { getUser } from '@/engine.server';
|
|
|
2
2
|
import { getSpec } from '@/config/spec-helpers';
|
|
3
3
|
import { API_ENDPOINTS } from '@/config';
|
|
4
4
|
import { can } from '@/services/permission.service';
|
|
5
|
-
import {
|
|
5
|
+
import { get, create, update, remove, listWithPagination, search } from '@/lib/busybase-store';
|
|
6
6
|
import { validateEntity, validateUpdate, hasErrors } from '@/lib/validate';
|
|
7
7
|
import { broadcastUpdate } from '@/lib/realtime-server';
|
|
8
8
|
import { UnauthorizedError, PermissionError, NotFoundError, ValidationError, AppError } from '@/lib/error-handler';
|
|
@@ -24,16 +24,16 @@ const createHandler = (entity, action) => async (request, { params, searchParams
|
|
|
24
24
|
|
|
25
25
|
if (action === 'list') {
|
|
26
26
|
if (q) {
|
|
27
|
-
const items = search(entity, q);
|
|
27
|
+
const items = await search(entity, q);
|
|
28
28
|
return ok({ items });
|
|
29
29
|
}
|
|
30
|
-
const { items, pagination } = listWithPagination(entity, {}, page, pageSize);
|
|
30
|
+
const { items, pagination } = await listWithPagination(entity, {}, page, pageSize);
|
|
31
31
|
return paginated(items, pagination);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (action === 'get') {
|
|
35
35
|
if (!id) throw new AppError('ID required', 'BAD_REQUEST', HTTP.BAD_REQUEST);
|
|
36
|
-
const item = get(entity, id);
|
|
36
|
+
const item = await get(entity, id);
|
|
37
37
|
if (!item) throw NotFoundError(entity, id);
|
|
38
38
|
return ok(item);
|
|
39
39
|
}
|
|
@@ -43,31 +43,31 @@ const createHandler = (entity, action) => async (request, { params, searchParams
|
|
|
43
43
|
const errors = await validateEntity(spec, data);
|
|
44
44
|
if (hasErrors(errors)) throw new ValidationError('Validation failed', errors);
|
|
45
45
|
|
|
46
|
-
const result = create(entity, data, user);
|
|
46
|
+
const result = await create(entity, data, user);
|
|
47
47
|
broadcastUpdate(API_ENDPOINTS.entity(entity), 'create', result);
|
|
48
48
|
return created(result);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
if (action === 'update') {
|
|
52
52
|
if (!id) throw new AppError('ID required', 'BAD_REQUEST', HTTP.BAD_REQUEST);
|
|
53
|
-
const prev = get(entity, id);
|
|
53
|
+
const prev = await get(entity, id);
|
|
54
54
|
if (!prev) throw NotFoundError(entity, id);
|
|
55
55
|
|
|
56
56
|
const data = await request.json();
|
|
57
57
|
const errors = await validateUpdate(spec, id, data);
|
|
58
58
|
if (hasErrors(errors)) throw new ValidationError('Validation failed', errors);
|
|
59
59
|
|
|
60
|
-
update(entity, id, data, user);
|
|
61
|
-
const result = get(entity, id);
|
|
60
|
+
await update(entity, id, data, user);
|
|
61
|
+
const result = await get(entity, id);
|
|
62
62
|
broadcastUpdate(API_ENDPOINTS.entityId(entity, id), 'update', result);
|
|
63
63
|
return ok(result);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
if (action === 'delete') {
|
|
67
67
|
if (!id) throw new AppError('ID required', 'BAD_REQUEST', HTTP.BAD_REQUEST);
|
|
68
|
-
if (!get(entity, id)) throw NotFoundError(entity, id);
|
|
68
|
+
if (!(await get(entity, id))) throw NotFoundError(entity, id);
|
|
69
69
|
|
|
70
|
-
remove(entity, id);
|
|
70
|
+
await remove(entity, id);
|
|
71
71
|
broadcastUpdate(API_ENDPOINTS.entityId(entity, id), 'delete', { id });
|
|
72
72
|
return ok({ success: true });
|
|
73
73
|
}
|