thatcher 1.0.44 → 1.0.46

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 (82) hide show
  1. package/README.md +44 -30
  2. package/package.json +1 -1
  3. package/src/adapters/google-auth.js +0 -7
  4. package/src/adapters/google-drive.js +1 -3
  5. package/src/adapters/google-gmail.js +1 -0
  6. package/src/app/api/[entity]/[[...path]]/route.js +1 -1
  7. package/src/app/api/audit/logs/route.js +1 -1
  8. package/src/app/api/audit/permissions/route.js +0 -1
  9. package/src/app/api/audit/route.js +2 -4
  10. package/src/app/api/auth/google/callback/route.js +1 -1
  11. package/src/app/api/auth/google/route.js +1 -1
  12. package/src/app/api/auth/logout/route.js +1 -1
  13. package/src/app/api/csrf-token/route.js +1 -1
  14. package/src/app/api/domains/route.js +1 -1
  15. package/src/app/api/email/allocate/route.js +0 -1
  16. package/src/app/api/email/receive/route.js +1 -1
  17. package/src/app/api/files/[id]/route.js +1 -1
  18. package/src/app/api/health/route.js +1 -1
  19. package/src/cli.js +9 -4
  20. package/src/config/spec-helpers.js +0 -1
  21. package/src/engine.server.js +1 -1
  22. package/src/lib/alert-manager.js +1 -1
  23. package/src/lib/api-helpers.js +1 -1
  24. package/src/lib/auth-route-helpers.js +1 -1
  25. package/src/lib/business-rules-engine.js +3 -3
  26. package/src/lib/busybase/adapter.js +2 -2
  27. package/src/lib/busybase/store.js +1 -1
  28. package/src/lib/config-generator-engine.js +0 -2
  29. package/src/lib/crud-action-helpers.js +1 -1
  30. package/src/lib/crud-handlers.js +2 -2
  31. package/src/lib/csrf-protection.js +0 -1
  32. package/src/lib/debug-registry.js +1 -1
  33. package/src/lib/errors/recovery.js +0 -8
  34. package/src/lib/errors/wrap.js +1 -1
  35. package/src/lib/events-engine.js +1 -1
  36. package/src/lib/field-iterator.js +2 -2
  37. package/src/lib/field-registry.js +2 -1
  38. package/src/lib/hot-reload/index.js +2 -1
  39. package/src/lib/minifier.js +1 -1
  40. package/src/lib/next-shim.js +2 -2
  41. package/src/lib/perf.js +0 -2
  42. package/src/lib/request-trace.js +1 -4
  43. package/src/lib/route-resolver.js +1 -1
  44. package/src/lib/stage-pipeline.js +0 -1
  45. package/src/lib/static-server.js +1 -1
  46. package/src/lib/tracing.js +0 -3
  47. package/src/lib/validation/security-validators.js +2 -2
  48. package/src/lib/validation-middleware.js +1 -1
  49. package/src/lib/workflow-engine.js +1 -1
  50. package/src/lib/xstate-workflow-engine.js +1 -1
  51. package/src/server/server.js +5 -9
  52. package/src/services/collaborator-role.service.js +1 -3
  53. package/src/services/notification-engine.js +3 -3
  54. package/src/services/permission.service.js +1 -1
  55. package/src/ui/advanced-widgets.js +0 -3
  56. package/src/ui/auth-pages.js +1 -1
  57. package/src/ui/common-handlers.js +2 -2
  58. package/src/ui/component-engine.js +2 -2
  59. package/src/ui/dashboard-renderer.js +1 -1
  60. package/src/ui/dialog-engine.js +11 -9
  61. package/src/ui/engagement-cards.js +0 -1
  62. package/src/ui/engagement-dialogs.js +0 -2
  63. package/src/ui/entity-renderer.js +2 -3
  64. package/src/ui/event-delegation.js +1 -1
  65. package/src/ui/file-dialogs.js +0 -2
  66. package/src/ui/job-management-renderer.js +0 -1
  67. package/src/ui/layout.js +4 -4
  68. package/src/ui/page-handler-admin.js +2 -2
  69. package/src/ui/page-handler-helpers.js +3 -3
  70. package/src/ui/page-handler-rfi.js +1 -1
  71. package/src/ui/page-handler.js +1 -1
  72. package/src/ui/permissions-ui.js +0 -12
  73. package/src/ui/picker-dialogs.js +0 -1
  74. package/src/ui/review/comparison.js +1 -1
  75. package/src/ui/review/detail-renderer.js +1 -1
  76. package/src/ui/review/mwr.js +1 -1
  77. package/src/ui/review/widgets.js +0 -1
  78. package/src/ui/rfi-detail-renderer.js +1 -1
  79. package/src/ui/settings/home.js +1 -1
  80. package/src/ui/settings/review.js +0 -1
  81. package/src/ui/settings/teams.js +1 -1
  82. package/src/ui/spacing-system.js +1 -1
package/README.md CHANGED
@@ -133,36 +133,50 @@ const canEdit = await thatcher.can(user, thatcher.getEntitySpec('item'), 'edit')
133
133
 
134
134
  ```
135
135
  thatcher/
136
- ├── src/
137
- │ ├── index.js # Main entry: createThatcher()
138
- │ ├── cli.js # CLI commands
139
- │ ├── config/
140
- │ │ ├── config-loader.js # YAML loading & validation
141
- │ │ ├── spec-helpers.js # Entity spec utilities
142
- │ │ ├── env.js # Environment config
143
- │ │ └── constants.js # HTTP codes, statuses
144
- │ ├── lib/
145
- │ │ ├── busybase-store.js # busybase data layer (async CRUD)
146
- │ │ ├── query-engine.js # Read operations (GET, search)
147
- │ │ ├── query-engine-write.js # Write operations (CRUD)
148
- │ │ ├── config-generator-engine.js # Spec builder
149
- │ │ ├── hook-engine.js # Event system
150
- │ │ ├── workflow-engine.js # State machines
151
- │ │ ├── auth-middleware.js # Auth checks
152
- │ │ ├── crud-factory.js # Handler factory
153
- │ │ ├── crud-handlers.js # HTTP handlers
154
- │ │ ├── validate.js # Validation
155
- │ │ └── logger.js # Structured logging
156
- │ ├── services/
157
- │ │ └── permission.service.js # Authorization
158
- │ ├── adapters/
159
- │ │ ├── google-auth.js # Google OAuth
160
- │ │ └── google-drive.js # Drive file operations
161
- │ ├── plugins/
162
- │ │ └── index.js # Plugin auto-discovery
163
- │ └── server/
164
- │ └── server.js # HTTP server
165
- └── package.json
136
+ src/
137
+ index.js # Main entry: createThatcher()
138
+ engine.js / engine.server.js # Runtime engine, server-only auth/session surface
139
+ cli.js # CLI commands
140
+ config/
141
+ config-loader.js # YAML loading & validation
142
+ spec-helpers.js # Entity spec utilities
143
+ env.js # Environment config
144
+ constants.js # HTTP codes, statuses
145
+ lib/
146
+ busybase/
147
+ adapter.js # busybase connection/adapter layer
148
+ store.js # busybase data layer (async CRUD)
149
+ audit.js / audit-reads.js # Audit log writes/reads
150
+ lucia-adapter.js # Lucia auth session adapter
151
+ errors/
152
+ types.js # AppError and error type hierarchy
153
+ wrap.js # wrap() - unified handler wrapper (timeout/retry/logging)
154
+ recovery.js # Circuit breaker, checkpoint, retry-with-backoff
155
+ validation/
156
+ entity-validators.js / business-validators.js / format-validators.js / security-validators.js / file-validators.js
157
+ csrf.js / rate-limit.js
158
+ config-generator-engine.js # Spec builder
159
+ hook-engine.js # Event system
160
+ workflow-engine.js # State machines (legacy/canonical) - xstate-workflow-engine.js is the debug/test-only successor
161
+ auth-middleware.js # Auth checks
162
+ crud-factory.js # Handler factory
163
+ crud-handlers.js # HTTP handlers - read + write CRUD operations
164
+ logger.js # Structured logging
165
+ services/
166
+ permission.service.js # Authorization
167
+ collaborator-role.service.js
168
+ notification-engine.js / email-sender.js
169
+ adapters/
170
+ google-auth.js # Google OAuth
171
+ google-drive.js # Drive file operations
172
+ google-gmail.js # Gmail send/receive
173
+ plugins/
174
+ index.js # Plugin auto-discovery
175
+ app/api/ # Route handlers (Next.js-style file routing, [entity]/[[...path]] catch-all + explicit routes)
176
+ ui/ # Server-rendered HTML components + client-side scripts
177
+ server/
178
+ server.js # HTTP server
179
+ package.json
166
180
  ```
167
181
 
168
182
  ### How It Works
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thatcher",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
4
4
  "description": "A config-driven application framework for building data-intensive web apps without code.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -4,19 +4,12 @@ import { google } from 'googleapis';
4
4
  const log = createLogger('[GoogleAuth]');
5
5
  import { buildConfig } from '../config/env.js';
6
6
  import fs from 'fs';
7
- import path from 'path';
8
7
 
9
8
  let _oauth2Client = null;
10
9
  let _jwtClient = null;
11
10
 
12
11
  export function initGoogleAuth(config = null) {
13
12
  const cfg = config || buildConfig();
14
- const scopes = [
15
- 'https://www.googleapis.com/auth/userinfo.email',
16
- 'https://www.googleapis.com/auth/userinfo.profile',
17
- 'https://www.googleapis.com/auth/drive',
18
- 'https://www.googleapis.com/auth/gmail.send',
19
- ];
20
13
 
21
14
  // OAuth2 client for web flow
22
15
  _oauth2Client = new google.auth.OAuth2(
@@ -4,8 +4,6 @@ import path from 'path';
4
4
  import { getJWTClient, getOAuth2Client } from './google-auth.js';
5
5
  import { buildConfig } from '../config/env.js';
6
6
 
7
- const DRIVE_SCOPES = ['https://www.googleapis.com/auth/drive'];
8
-
9
7
  export function getDriveClient(user = null) {
10
8
  let client;
11
9
 
@@ -26,7 +24,7 @@ export function getDriveClient(user = null) {
26
24
  export async function uploadFile(filePath, name, options = {}, user = null) {
27
25
  const drive = getDriveClient(user);
28
26
  const absPath = path.resolve(filePath);
29
- const fileSize = fs.statSync(absPath).size;
27
+ fs.statSync(absPath);
30
28
 
31
29
  const res = await drive.files.create({
32
30
  requestBody: {
@@ -24,6 +24,7 @@ export function getGmailClient(user = null) {
24
24
 
25
25
  function encodeHeader(value) {
26
26
  // RFC 2047 encoded-word for any non-ASCII header value (subject/name).
27
+ // eslint-disable-next-line no-control-regex -- \x00-\x7F is the intentional ASCII range check, not a stray control char
27
28
  if (/^[\x00-\x7F]*$/.test(value)) return value;
28
29
  return `=?UTF-8?B?${Buffer.from(value, 'utf8').toString('base64')}?=`;
29
30
  }
@@ -1,7 +1,7 @@
1
1
  import { createCrudHandlers } from '@/lib/crud-factory';
2
2
  import { setCurrentRequest } from '@/engine.server';
3
3
 
4
- function wrapHandler(method) {
4
+ function wrapHandler(_method) {
5
5
  return async (request, context) => {
6
6
  setCurrentRequest(request);
7
7
  const params = await context.params;
@@ -1,6 +1,6 @@
1
1
  import { NextResponse } from '@/lib/next-shim';
2
2
  import { requireAuth } from '@/lib/auth-middleware';
3
- import { searchLogs, getLogStats, rotateLogsOlderThan } from '@/lib/audit-logger-enhanced';
3
+ import { searchLogs, rotateLogsOlderThan } from '@/lib/audit-logger-enhanced';
4
4
  import { withErrorHandler } from '@/lib/errors';
5
5
 
6
6
  export const GET = withErrorHandler(async (request) => {
@@ -33,7 +33,6 @@ export async function GET(request) {
33
33
  const search = searchParams.get('search');
34
34
  const startDate = searchParams.get('start_date');
35
35
  const endDate = searchParams.get('end_date');
36
- const action = searchParams.get('action');
37
36
  const limit = parseInt(searchParams.get('limit') || '100', 10);
38
37
  const offset = parseInt(searchParams.get('offset') || '0', 10);
39
38
  const format = searchParams.get('format');
@@ -1,13 +1,11 @@
1
- import { requireAuth, requirePermission } from '@/lib/auth-middleware';
1
+ import { requireAuth } from '@/lib/auth-middleware';
2
2
  import { getAuditHistory, getActionStats, getUserStats } from '@/lib/busybase/audit-reads';
3
- import { getSpec } from '@/config/spec-helpers';
4
3
  import { paginated, ok } from '@/lib/response-formatter';
5
4
  import { withErrorHandler, AppError } from '@/lib/errors';
6
5
  import { HTTP } from '@/config/constants';
7
- import { parse as parseQuery } from '@/lib/query-string-adapter';
8
6
 
9
7
  export const GET = withErrorHandler(async (request) => {
10
- const user = await requireAuth();
8
+ await requireAuth();
11
9
  const url = new URL(request.url);
12
10
 
13
11
  const entityType = url.searchParams.get('entityType');
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from '@/lib/logger.js';
2
- import { google, createSession } from '@/engine.server';
2
+ import { createSession } from '@/engine.server';
3
3
 
4
4
  const log = createLogger('[OAuth]');
5
5
  import { getBy, create } from '@/engine';
@@ -50,7 +50,7 @@ export async function GET(request) {
50
50
  });
51
51
  }
52
52
 
53
- export async function HEAD(request) {
53
+ export async function HEAD(_request) {
54
54
  try {
55
55
  const { valid } = validateOAuthProvider(google);
56
56
  return new Response(null, { status: valid ? 200 : 503 });
@@ -8,7 +8,7 @@ function buildLogoutHeaders(headerMap) {
8
8
  : [];
9
9
  }
10
10
 
11
- export async function GET(request, { res } = {}) {
11
+ export async function GET(request) {
12
12
  try {
13
13
  setEngineRequest(request);
14
14
  setCurrentRequest(request);
@@ -2,7 +2,7 @@ import { generateToken } from '@/lib/csrf-protection';
2
2
  import { ok } from '@/lib/response-formatter';
3
3
  import { withErrorHandler } from '@/lib/errors';
4
4
 
5
- export const GET = withErrorHandler(async (request) => {
5
+ export const GET = withErrorHandler(async (_request) => {
6
6
  const token = generateToken();
7
7
  return ok({ csrfToken: token });
8
8
  }, 'CSRF:GetToken');
@@ -6,7 +6,7 @@ import { ok } from '@/lib/response-formatter';
6
6
  import { withErrorHandler } from '@/lib/errors';
7
7
  import { getConfigEngine } from '@/lib/config-generator-engine';
8
8
 
9
- export const GET = withErrorHandler(async (request) => {
9
+ export const GET = withErrorHandler(async (_request) => {
10
10
  await getConfigEngine();
11
11
  const domainLoader = getDomainLoader();
12
12
  const validDomains = domainLoader.getValidDomains();
@@ -7,7 +7,6 @@ const log = createLogger('[EmailAllocate]');
7
7
  import {
8
8
  allocateEmailToEntity,
9
9
  autoAllocateEmail,
10
- validateAllocation,
11
10
  findEntityByAlternateId,
12
11
  } from '@/lib/email-parser';
13
12
 
@@ -143,7 +143,7 @@ export async function POST(request) {
143
143
  }
144
144
  }
145
145
 
146
- export async function GET(request) {
146
+ export async function GET(_request) {
147
147
  return NextResponse.json({
148
148
  endpoint: 'Email Receive Webhook',
149
149
  method: 'POST',
@@ -27,7 +27,7 @@ export async function GET(request, { params }) {
27
27
 
28
28
  const content = await fileService.download(fileRecord.drive_file_id);
29
29
 
30
- const safeName = (fileRecord.file_name || 'download').replace(/[^\w.\-]/g, '_');
30
+ const safeName = (fileRecord.file_name || 'download').replace(/[^\w.-]/g, '_');
31
31
  return new NextResponse(content, {
32
32
  headers: {
33
33
  'Content-Type': fileRecord.mime_type || 'application/octet-stream',
@@ -85,7 +85,7 @@ export const GET = async (request) => {
85
85
  }
86
86
  }
87
87
 
88
- export const HEAD = async (request) => {
88
+ export const HEAD = async (_request) => {
89
89
  try {
90
90
  await count('user', {})
91
91
  return new Response(null, { status: 200 })
package/src/cli.js CHANGED
@@ -11,8 +11,10 @@
11
11
  * thatcher example - Generate example config
12
12
  */
13
13
 
14
- import { startThatcher, Thatcher } from './index.js';
14
+ import { Thatcher } from './index.js';
15
15
  import * as readline from 'readline';
16
+ import fs from 'fs';
17
+ import path from 'path';
16
18
 
17
19
  const args = process.argv.slice(2);
18
20
  const command = args[0];
@@ -20,7 +22,7 @@ const command = args[0];
20
22
  async function main() {
21
23
  switch (command) {
22
24
  case 'start':
23
- case 'dev':
25
+ case 'dev': {
24
26
  console.log(`[Thatcher] Starting in ${command} mode...`);
25
27
  const thatcher = new Thatcher({
26
28
  server: { hotReload: command === 'dev' },
@@ -34,15 +36,17 @@ async function main() {
34
36
  process.exit(1);
35
37
  }
36
38
  break;
39
+ }
37
40
 
38
- case 'migrate':
41
+ case 'migrate': {
39
42
  console.log('[Thatcher] Running migrations...');
40
43
  const t1 = new Thatcher({});
41
44
  await t1.init();
42
45
  console.log('[Thatcher] Migrations complete');
43
46
  break;
47
+ }
44
48
 
45
- case 'validate':
49
+ case 'validate': {
46
50
  console.log('[Thatcher] Validating configuration...');
47
51
  try {
48
52
  const t2 = new Thatcher({});
@@ -55,6 +59,7 @@ async function main() {
55
59
  process.exit(1);
56
60
  }
57
61
  break;
62
+ }
58
63
 
59
64
  case 'console':
60
65
  case 'repl':
@@ -135,7 +135,6 @@ export function buildNavigation(configEngine, user = null) {
135
135
  // Filter by user permissions if provided
136
136
  if (user && configEngine.getRolePermissions) {
137
137
  return items.filter(item => {
138
- const spec = configEngine.generateEntitySpec(item.name);
139
138
  const perms = configEngine.getRolePermissions(user.role, item.name);
140
139
  return perms.includes('list') || perms.includes('view') || perms.includes('all');
141
140
  });
@@ -104,7 +104,7 @@ export async function createSession(userId) {
104
104
  }
105
105
 
106
106
  export async function invalidateSession() {
107
- const lucia = getLucia();
107
+ getLucia();
108
108
  // Simplified - caller handles cookie clearing
109
109
  // In full app this uses @lucia-auth/adapter-sqlite's cookie management
110
110
  }
@@ -22,7 +22,7 @@ function checkErrorRate(errorMetrics) {
22
22
  const window = thresholds.errorRate.window
23
23
  let recentErrors = 0
24
24
 
25
- for (const [endpoint, data] of Object.entries(errorMetrics.byEndpoint)) {
25
+ for (const [, data] of Object.entries(errorMetrics.byEndpoint)) {
26
26
  const recent = (data.recent || []).filter(e => (now - e.ts) < window)
27
27
  recentErrors += recent.length
28
28
  }
@@ -30,7 +30,7 @@ export const ok = (data) => NextResponse.json(withMetadata(data, HTTP.OK, 'succe
30
30
  export const created = (data) => NextResponse.json(withMetadata(data, HTTP.CREATED, 'created'), { status: HTTP.CREATED });
31
31
  export const notFound = (entity = 'Resource') => NextResponse.json(withMetadata({ error: ERROR_MESSAGES.notFound(entity) }, HTTP.NOT_FOUND, 'error'), { status: HTTP.NOT_FOUND });
32
32
  export const badRequest = (reason = 'invalid data') => NextResponse.json(withMetadata({ error: ERROR_MESSAGES.invalidRequest(reason) }, HTTP.BAD_REQUEST, 'error'), { status: HTTP.BAD_REQUEST });
33
- export const unauthorized = (action = 'perform this action') => NextResponse.json(withMetadata({ error: ERROR_MESSAGES.permission.denied }, HTTP.FORBIDDEN, 'error'), { status: HTTP.FORBIDDEN });
33
+ export const unauthorized = (_action = 'perform this action') => NextResponse.json(withMetadata({ error: ERROR_MESSAGES.permission.denied }, HTTP.FORBIDDEN, 'error'), { status: HTTP.FORBIDDEN });
34
34
  export const serverError = (msg = null) => NextResponse.json(withMetadata({ error: msg || ERROR_MESSAGES.operationFailed('server operation') }, HTTP.INTERNAL_ERROR, 'error'), { status: HTTP.INTERNAL_ERROR });
35
35
 
36
36
  export async function withEntityAccess(entity, action, handler) {
@@ -23,7 +23,7 @@ export function validateOAuthProvider(provider) {
23
23
  return { valid: true };
24
24
  }
25
25
 
26
- export async function setOAuthCookie(name, value, options = {}) {
26
+ export async function setOAuthCookie(name, value) {
27
27
  const timestamp = Date.now();
28
28
  const key = `oauth-${timestamp}-${Math.random().toString(36).substring(7)}`;
29
29
  const expiresAt = timestamp + (SESSION.cookieMaxAge * 1000);
@@ -11,7 +11,7 @@ export class BusinessRulesEngine {
11
11
  return rule && !this.isDuplicateEngagement(engagement);
12
12
  });
13
13
 
14
- this.registerRule('daysOutstandingNotificationNeeded', (rfi) => {
14
+ this.registerRule('daysOutstandingNotificationNeeded', (_rfi) => {
15
15
  return this.businessRules.rfiDaysOutstanding.enabled;
16
16
  });
17
17
 
@@ -23,7 +23,7 @@ export class BusinessRulesEngine {
23
23
  return true;
24
24
  });
25
25
 
26
- this.registerRule('clientCanRate', (user, engagement) => {
26
+ this.registerRule('clientCanRate', (user, _engagement) => {
27
27
  return user.type === 'client' && user.role === 'client_admin';
28
28
  });
29
29
 
@@ -66,7 +66,7 @@ export class BusinessRulesEngine {
66
66
  return this.businessRules.recreationRules.engagement[interval];
67
67
  }
68
68
 
69
- isDuplicateEngagement(engagement) {
69
+ isDuplicateEngagement(_engagement) {
70
70
  return false;
71
71
  }
72
72
 
@@ -32,7 +32,7 @@ export class BusyBaseAdapter {
32
32
  this._client = this._embedded;
33
33
  logger.info('Embedded mode initialized', { dir: this.config.dir });
34
34
  } else {
35
- const { default: BB, createClient } = await import('busybase');
35
+ const { default: BB } = await import('busybase');
36
36
  this._client = BB(this.config.url, this.config.project);
37
37
  logger.info('Remote mode initialized', { url: this.config.url });
38
38
  }
@@ -106,7 +106,7 @@ export class BusyBaseAdapter {
106
106
  }
107
107
 
108
108
  async unsubscribeAll() {
109
- for (const [name, { channel }] of _realtimeChannels.entries()) {
109
+ for (const [, { channel }] of _realtimeChannels.entries()) {
110
110
  channel.unsubscribe();
111
111
  }
112
112
  _realtimeChannels.clear();
@@ -271,7 +271,7 @@ export async function create(entity, data, user) {
271
271
  // unconditional-write behaviour, unchanged for every existing caller; _version
272
272
  // is added as a new column, ignored by every reader that doesn't ask for it.
273
273
  export async function update(entity, id, data, opts = {}) {
274
- const spec = specOf(entity);
274
+ specOf(entity);
275
275
  const tbl = tableName(entity);
276
276
 
277
277
  const existing = await get(entity, id);
@@ -12,9 +12,7 @@
12
12
  // as of this writing, vs. workflow-engine.js's 2 and
13
13
  // xstate-workflow-engine.js's 2 debug/test-only importers) -- this is live,
14
14
  // central infrastructure, not a candidate for deprecation.
15
- import { load as yamlLoad } from 'js-yaml';
16
15
  import { LRUCache, deepFreeze, deepClone, recursiveResolve } from './config-helpers.js';
17
- import { generateFieldsFromOverrides, ensureFieldLabels } from './config-field-helpers.js';
18
16
 
19
17
  export class ConfigGeneratorEngine {
20
18
  constructor(masterConfig) {
@@ -9,7 +9,7 @@ const ACTION_FIELD_MAPS = {
9
9
  respond: (data, user) => ({ status: 'responded', response: data.response, responded_at: now(), responded_by: user.id }),
10
10
  };
11
11
 
12
- export function resolveActionUpdate(action, data, user, record) {
12
+ export function resolveActionUpdate(action, data, user, _record) {
13
13
  const mapped = ACTION_FIELD_MAPS[action];
14
14
  if (typeof mapped === 'function') return mapped(data, user);
15
15
  if (mapped) {
@@ -3,10 +3,10 @@ import { get, listWithPagination, searchWithPagination, create, update, remove }
3
3
 
4
4
  const log = createLogger('[CRUD]');
5
5
  import { validateEntity, validateUpdate, sanitizeData } from './validation/index.js';
6
- import { requirePermission, getSessionToken } from './auth-middleware.js';
6
+ import { requirePermission } from './auth-middleware.js';
7
7
  import { executeHook } from './hook-engine.js';
8
8
  import { AppError, NotFoundError, ValidationError } from './errors/index.js';
9
- import { ok, created, paginated, noContent, error } from './response-formatter.js';
9
+ import { ok, created, paginated, noContent } from './response-formatter.js';
10
10
  import { HTTP } from '../config/constants.js';
11
11
  import { permissionService } from '../services/permission.service.js';
12
12
  import { parse as parseQuery } from './query-string-adapter.js';
@@ -43,7 +43,6 @@ export async function withCsrfValidation(handler) {
43
43
  }
44
44
 
45
45
  try {
46
- const contentType = request.headers.get('content-type') || '';
47
46
  const token = request.headers.get('x-csrf-token');
48
47
 
49
48
  if (!token || !validateToken(token)) {
@@ -1,6 +1,6 @@
1
1
  import { createLogger } from './logger.js';
2
2
 
3
- const logger = createLogger('[DebugRegistry]');
3
+ createLogger('[DebugRegistry]');
4
4
 
5
5
  class DebugRegistry {
6
6
  constructor() {
@@ -43,8 +43,6 @@ export async function withCircuitBreaker(name, fn, options = {}) {
43
43
 
44
44
  try {
45
45
  const result = await fn();
46
- if (breaker.state === 'half-open') {
47
- }
48
46
  breaker.failures = 0;
49
47
  breaker.state = 'closed';
50
48
  return result;
@@ -248,12 +246,6 @@ export async function withRecovery(fn, options = {}) {
248
246
 
249
247
  let operation = fn;
250
248
 
251
- if (checkpointName) {
252
- const savedState = restoreCheckpoint(checkpointName);
253
- if (savedState) {
254
- }
255
- }
256
-
257
249
  if (supervisor) {
258
250
  operation = () => supervise(supervisor, operation, options);
259
251
  }
@@ -48,7 +48,7 @@ export function wrap(fn, options = {}) {
48
48
  // retryWithBackoff lives here (not recovery.js) because wrap() itself needs it
49
49
  // with no circular dependency; recovery.js imports it back from here.
50
50
  export async function retryWithBackoff(fn, options = {}) {
51
- const { maxAttempts = 3, delay = 1000, backoff = 2, context = {} } = options;
51
+ const { maxAttempts = 3, delay = 1000, backoff = 2 } = options;
52
52
 
53
53
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
54
54
  try {
@@ -175,7 +175,7 @@ export const registerEntityHandlers = () => {
175
175
  create('removed_highlight', { review_id: highlight.review_id, original_id: highlight.id, highlight_data: JSON.stringify(highlight) }, user);
176
176
  });
177
177
 
178
- hookEngine.on('collaborator:afterCreate', async (collaborator, user) => {
178
+ hookEngine.on('collaborator:afterCreate', async (collaborator, _user) => {
179
179
  const review = get('review', collaborator.review_id);
180
180
  const collabUser = get('user', collaborator.user_id);
181
181
  if (review && collabUser) {
@@ -20,13 +20,13 @@ export function getFieldNames(spec) {
20
20
 
21
21
  export function getEditableFields(spec) {
22
22
  return Object.entries(spec.fields || {})
23
- .filter(([key, field]) => !field.readonly && !field.auto && !field.auto_generate)
23
+ .filter(([, field]) => !field.readonly && !field.auto && !field.auto_generate)
24
24
  .map(([key]) => key);
25
25
  }
26
26
 
27
27
  export function getFieldsByType(spec, type) {
28
28
  return Object.entries(spec.fields || {})
29
- .filter(([key, field]) => field.type === type);
29
+ .filter(([, field]) => field.type === type);
30
30
  }
31
31
 
32
32
  export function fieldQuery(spec, predicates, options = {}) {
@@ -11,9 +11,10 @@ export function coerceFieldValue(value, type) {
11
11
  case 'json':
12
12
  return typeof value === 'string' ? JSON.parse(value) : value;
13
13
  case 'date':
14
- case 'timestamp':
14
+ case 'timestamp': {
15
15
  const num = Number(value);
16
16
  return isNaN(num) ? null : num;
17
+ }
17
18
  case 'ref':
18
19
  return String(value);
19
20
  default:
@@ -41,7 +41,7 @@ const __dirname = path.dirname(__filename);
41
41
  const require = createRequire(import.meta.url);
42
42
 
43
43
  const promiseContainerMod = require('./promise-container.js');
44
- export { Mutex, MutexManager, globalManager } from './mutex.js';
44
+ import { Mutex, MutexManager, globalManager } from './mutex.js';
45
45
  import { Supervisor, SupervisorTree, globalTree } from './supervisor.js';
46
46
  const checkpointMod = require('./checkpoint.js');
47
47
  const timeoutMod = require('./timeout-wrapper.js');
@@ -72,6 +72,7 @@ expose('hotReload', {
72
72
  }, 'Hot reload infrastructure');
73
73
 
74
74
  export {
75
+ Mutex, MutexManager, globalManager,
75
76
  PromiseContainer, globalContainer, contain,
76
77
  Supervisor, SupervisorTree, globalTree,
77
78
  CheckpointManager, globalCheckpoint,
@@ -5,7 +5,7 @@ export function minifyJS(code) {
5
5
  .replace(/^\s+/gm, '')
6
6
  .replace(/\s+$/gm, '')
7
7
  .replace(/\n+/g, '\n')
8
- .replace(/\s*([{}()\[\];:,=<>!+\-*\/&|?])\s*/g, '$1')
8
+ .replace(/\s*([{}()[\];:,=<>!+\-*/&|?])\s*/g, '$1')
9
9
  .trim();
10
10
  }
11
11
 
@@ -262,9 +262,9 @@ export async function cookies() {
262
262
 
263
263
  export function headers() {
264
264
  return {
265
- get: (name) => null,
265
+ get: (_name) => null,
266
266
  getSetCookie: () => [],
267
- has: (name) => false,
267
+ has: (_name) => false,
268
268
  entries: () => [],
269
269
  };
270
270
  }
package/src/lib/perf.js CHANGED
@@ -22,7 +22,6 @@ const logger = createLogger('[Perf]');
22
22
  const _profiles = new Map();
23
23
  const _alerts = [];
24
24
  const MAX_ALERTS = 100;
25
- const BASELINE_WINDOW_MS = 3600000;
26
25
 
27
26
  const DEFAULT_THRESHOLDS = {
28
27
  'http.request': 500,
@@ -119,7 +118,6 @@ export class PerfProfiler {
119
118
  if (!profile) return null;
120
119
 
121
120
  const durations = profile.durations.map(d => d.durationMs).sort((a, b) => a - b);
122
- const count = durations.length;
123
121
 
124
122
  return {
125
123
  operation: profile.operation,
@@ -6,11 +6,8 @@
6
6
 
7
7
  import { tracer } from './tracing.js';
8
8
  import { perfProfiler } from './perf.js';
9
- import { createLogger } from './logger.js';
10
9
  import { recordRequest, recordError } from './metrics-collector.js';
11
10
 
12
- const logger = createLogger('[RequestTracing]');
13
-
14
11
  // ---------------------------------------------------------------------------
15
12
  // From request-tracing.js
16
13
  // ---------------------------------------------------------------------------
@@ -223,7 +220,7 @@ export default withTracing;
223
220
  // ---------------------------------------------------------------------------
224
221
  // From request-tracker.js
225
222
  // ---------------------------------------------------------------------------
226
- function wrapHandler(handler, metadata = {}) {
223
+ function wrapHandler(handler, _metadata = {}) {
227
224
  return async (request, context) => {
228
225
  const start = process.hrtime.bigint();
229
226
  const method = request.method;