thatcher 1.0.7 → 1.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +4 -16
  2. package/package.json +12 -18
  3. package/src/adapters/google-auth.js +4 -43
  4. package/src/adapters/google-drive.js +12 -67
  5. package/src/app/api/audit/dashboard/route.js +14 -11
  6. package/src/app/api/audit/logs/route.js +2 -2
  7. package/src/app/api/audit/permissions/[id]/route.js +6 -3
  8. package/src/app/api/audit/permissions/route.js +10 -7
  9. package/src/app/api/audit/permissions/stats/route.js +8 -5
  10. package/src/app/api/audit/route.js +4 -4
  11. package/src/app/api/audit/stats/route.js +1 -1
  12. package/src/app/api/auth/google/callback/route.js +5 -2
  13. package/src/app/api/auth/google/route.js +4 -1
  14. package/src/app/api/auth/login/route.js +5 -2
  15. package/src/app/api/auth/mwr-bridge/route.js +11 -21
  16. package/src/app/api/auth/password-reset/route.js +15 -14
  17. package/src/app/api/cron/trigger/route.js +4 -1
  18. package/src/app/api/debug/[[...path]]/route.js +6 -5
  19. package/src/app/api/debug/sqlite/route.js +7 -12
  20. package/src/app/api/debug/sync/route.js +5 -5
  21. package/src/app/api/domains/route.js +4 -1
  22. package/src/app/api/email/allocate/batch/route.js +19 -29
  23. package/src/app/api/email/allocate/route.js +16 -17
  24. package/src/app/api/email/receive/route.js +31 -42
  25. package/src/app/api/email/send/route.js +28 -16
  26. package/src/app/api/email/unallocated/route.js +10 -13
  27. package/src/app/api/files/[id]/route.js +4 -1
  28. package/src/app/api/formula/[[...path]]/route.js +0 -4
  29. package/src/app/api/health/route.js +10 -9
  30. package/src/app/api/metrics/route.js +1 -1
  31. package/src/cli.js +4 -4
  32. package/src/config/config-loader.js +1 -21
  33. package/src/config/constants.js +0 -35
  34. package/src/config/env.js +0 -30
  35. package/src/config/spec-helpers.js +6 -95
  36. package/src/engine.js +23 -0
  37. package/src/engine.server.js +8 -78
  38. package/src/index.js +42 -67
  39. package/src/lib/alert-manager.js +7 -3
  40. package/src/lib/api-helpers.js +4 -3
  41. package/src/lib/api.js +10 -10
  42. package/src/lib/audit-logger-enhanced.js +77 -83
  43. package/src/lib/auth-middleware.js +1 -32
  44. package/src/lib/auth-route-helpers.js +5 -0
  45. package/src/lib/busybase-adapter.js +0 -5
  46. package/src/lib/busybase-audit-reads.js +142 -0
  47. package/src/lib/busybase-audit.js +41 -0
  48. package/src/lib/busybase-lucia-adapter.js +72 -0
  49. package/src/lib/busybase-store.js +338 -0
  50. package/src/lib/config-field-helpers.js +0 -17
  51. package/src/lib/config-generator-engine.js +19 -36
  52. package/src/lib/config-helpers.js +0 -21
  53. package/src/lib/crud-action-helpers.js +2 -2
  54. package/src/lib/crud-factory.js +1 -16
  55. package/src/lib/crud-handlers.js +17 -52
  56. package/src/lib/csrf-protection.js +5 -2
  57. package/src/lib/date-utils.js +3 -127
  58. package/src/lib/debug-registry.js +0 -5
  59. package/src/lib/domain-loader.js +4 -1
  60. package/src/lib/email-sender.js +25 -23
  61. package/src/lib/error-handler.js +5 -41
  62. package/src/lib/error-recovery.js +10 -7
  63. package/src/lib/error-resilience.js +7 -5
  64. package/src/lib/events-engine.js +9 -6
  65. package/src/lib/export-sink.js +0 -5
  66. package/src/lib/field-iterator.js +7 -76
  67. package/src/lib/field-registry.js +0 -22
  68. package/src/lib/generic-crud-handler.js +0 -16
  69. package/src/lib/hook-engine.js +5 -53
  70. package/src/lib/hot-reload/checkpoint.js +5 -2
  71. package/src/lib/hot-reload/promise-container.js +7 -3
  72. package/src/lib/hot-reload/route-wrapper.js +4 -1
  73. package/src/lib/hot-reload/supervisor.js +6 -3
  74. package/src/lib/hyperformula-service.js +0 -5
  75. package/src/lib/id-helpers.js +10 -0
  76. package/src/lib/index.js +4 -7
  77. package/src/lib/log-aggregator.js +0 -1
  78. package/src/lib/logger.js +0 -15
  79. package/src/lib/metrics-collector.js +2 -46
  80. package/src/lib/next-compat.js +15 -1
  81. package/src/lib/observability-bootstrap.js +0 -5
  82. package/src/lib/perf-monitor.js +4 -1
  83. package/src/lib/perf-profiler.js +0 -5
  84. package/src/lib/query-string-adapter.js +5 -47
  85. package/src/lib/realtime-server.js +4 -26
  86. package/src/lib/request-tracing.js +0 -5
  87. package/src/lib/resource-monitor.js +4 -1
  88. package/src/lib/response-formatter.js +0 -38
  89. package/src/lib/route-helpers.js +3 -1
  90. package/src/lib/safe-json.js +5 -1
  91. package/src/lib/server-bootstrap.js +10 -9
  92. package/src/lib/stage-pipeline.js +2 -2
  93. package/src/lib/state-transport-client.js +4 -1
  94. package/src/lib/static-server.js +2 -2
  95. package/src/lib/status-helpers.js +2 -51
  96. package/src/lib/tracing.js +0 -5
  97. package/src/lib/utils.js +6 -67
  98. package/src/lib/validate.js +5 -88
  99. package/src/lib/validators.js +0 -38
  100. package/src/lib/with-audit-logging.js +1 -1
  101. package/src/lib/workflow-engine.js +10 -77
  102. package/src/lib/xstate-workflow-engine.js +3 -9
  103. package/src/plugins/index.js +5 -3
  104. package/src/server/server.js +26 -27
  105. package/src/services/collaborator-role.service.js +21 -77
  106. package/src/services/email-sender.js +7 -34
  107. package/src/services/notification-engine.js +12 -33
  108. package/src/services/permission.service.js +10 -73
  109. package/src/ui/advanced-search-renderer.js +3 -2
  110. package/src/ui/advanced-widgets.js +1 -1
  111. package/src/ui/auth-pages.js +10 -9
  112. package/src/ui/auth-styles.js +1 -1
  113. package/src/ui/client.js +1 -1
  114. package/src/ui/component-engine.js +1 -1
  115. package/src/ui/dashboard-renderer.js +5 -5
  116. package/src/ui/dialog-engine.js +6 -9
  117. package/src/ui/dialog-factory.js +2 -2
  118. package/src/ui/engagement-cards.js +7 -7
  119. package/src/ui/engagement-dialogs.js +2 -3
  120. package/src/ui/engagement-grid-renderer.js +20 -19
  121. package/src/ui/entity-renderer.js +56 -42
  122. package/src/ui/file-dialogs.js +25 -9
  123. package/src/ui/flexup-report-renderer.js +21 -18
  124. package/src/ui/format-helpers.js +27 -28
  125. package/src/ui/highlight-threading-renderer.js +9 -9
  126. package/src/ui/job-management-renderer.js +18 -16
  127. package/src/ui/layout.js +51 -25
  128. package/src/ui/ml-console-renderer.js +15 -16
  129. package/src/ui/monitoring-dashboard-client.js +9 -8
  130. package/src/ui/notifications-renderer.js +11 -10
  131. package/src/ui/page-handler-admin.js +32 -23
  132. package/src/ui/page-handler-helpers.js +27 -23
  133. package/src/ui/page-handler-reviews.js +71 -35
  134. package/src/ui/page-handler-rfi.js +28 -14
  135. package/src/ui/page-handler.js +65 -48
  136. package/src/ui/picker-dialogs.js +26 -19
  137. package/src/ui/render-helpers.js +84 -46
  138. package/src/ui/review-comparison-renderer.js +8 -7
  139. package/src/ui/review-detail-panels.js +8 -9
  140. package/src/ui/review-detail-renderer.js +3 -3
  141. package/src/ui/review-detail-script.js +13 -12
  142. package/src/ui/review-mwr-renderer.js +6 -5
  143. package/src/ui/review-renderer.js +7 -6
  144. package/src/ui/review-widgets.js +3 -3
  145. package/src/ui/rfi-detail-renderer.js +37 -20
  146. package/src/ui/rfi-report-renderer.js +14 -9
  147. package/src/ui/settings-renderer-advanced.js +21 -18
  148. package/src/ui/settings-renderer-advanced2.js +20 -19
  149. package/src/ui/settings-renderer-teams.js +4 -4
  150. package/src/ui/settings-renderer.js +4 -4
  151. package/src/ui/spacing-system.js +5 -2
  152. package/src/ui/standalone-login.js +2 -2
  153. package/src/ui/styles.css +2 -1
  154. package/src/ui/styles2.css +75 -5
  155. package/src/ui/test-page.js +4 -4
  156. package/src/ui/widgets.js +20 -15
  157. package/src/lib/audit-logger.js +0 -193
  158. package/src/lib/database-core.js +0 -255
  159. package/src/lib/database-migrations.js +0 -350
  160. package/src/lib/db-backup.js +0 -97
  161. package/src/lib/error-boundary.js +0 -134
  162. package/src/lib/errors.js +0 -69
  163. package/src/lib/health-monitor.js +0 -134
  164. package/src/lib/index-optimizer.js +0 -96
  165. package/src/lib/query-engine-write.js +0 -221
  166. package/src/lib/query-engine.js +0 -446
  167. package/src/ui/collaboration-dialogs.js +0 -31
  168. package/src/ui/global-tags.js +0 -144
  169. package/src/ui/idle-logout.js +0 -156
  170. package/src/ui/letter-dialogs.js +0 -37
  171. package/src/ui/password-reset-page.js +0 -146
  172. package/src/ui/perf-renderer.js +0 -71
  173. package/src/ui/rfi-renderer.js +0 -194
  174. package/src/ui/validation-ui.js +0 -147
@@ -1,5 +1,8 @@
1
1
  import { NextResponse } from '@/lib/next-polyfills';
2
- import { getDatabase } from '@/lib/database-core';
2
+ import { createLogger } from '@/lib/logger.js';
3
+ import { list } from '@/engine';
4
+
5
+ const log = createLogger('[EmailUnallocated]');
3
6
 
4
7
  export async function GET(request) {
5
8
  try {
@@ -11,17 +14,11 @@ export async function GET(request) {
11
14
  const limit = parseInt(searchParams.get('limit') || '50', 10);
12
15
  const offset = parseInt(searchParams.get('offset') || '0', 10);
13
16
 
14
- const db = getDatabase();
15
-
16
- const emails = db.prepare(`
17
- SELECT *
18
- FROM email
19
- WHERE allocated = 0
20
- ORDER BY received_at DESC
21
- LIMIT ? OFFSET ?
22
- `).all(limit, offset);
23
-
24
- const total = db.prepare('SELECT COUNT(*) as count FROM email WHERE allocated = 0').get();
17
+ // Unallocated emails, newest first, paginated (busybase + in-memory slice).
18
+ const all = (await list('email', {}, { sort: { field: 'received_at', dir: 'DESC' } }))
19
+ .filter(e => !e.allocated || e.allocated === 0);
20
+ const total = { count: all.length };
21
+ const emails = all.slice(offset, offset + limit);
25
22
 
26
23
  const emailsWithParsedAttachments = emails.map(email => ({
27
24
  ...email,
@@ -38,7 +35,7 @@ export async function GET(request) {
38
35
  });
39
36
 
40
37
  } catch (error) {
41
- console.error('[EMAIL_UNALLOCATED] Error:', error);
38
+ log.error('error:', { message: error.message });
42
39
  return NextResponse.json(
43
40
  { error: 'Internal server error', message: error.message },
44
41
  { status: 500 }
@@ -1,4 +1,7 @@
1
1
  import { NextResponse } from '@/lib/next-polyfills';
2
+ import { createLogger } from '@/lib/logger.js';
3
+
4
+ const log = createLogger('[FilesAPI]');
2
5
  import { requireUser } from '@/engine.server';
3
6
  import { permissionService } from '@/services/permission.service';
4
7
  import { get } from '@/engine';
@@ -32,7 +35,7 @@ export async function GET(request, { params }) {
32
35
  },
33
36
  });
34
37
  } catch (error) {
35
- console.error('File download error:', error);
38
+ log.error('file download error:', { message: error.message });
36
39
  return NextResponse.json({ error: error.message }, { status: HTTP.INTERNAL_ERROR });
37
40
  }
38
41
  }
@@ -1,7 +1,3 @@
1
- /**
2
- * Formula API Routes - HyperFormula operations via HTTP
3
- */
4
-
5
1
  import { getHyperFormulaService, createHyperFormulaService } from '@/lib/hyperformula-service.js';
6
2
  import { createLogger } from '@/lib/logger.js';
7
3
 
@@ -1,6 +1,9 @@
1
+ import { createLogger } from '@/lib/logger.js';
1
2
  import fs from 'fs'
3
+
4
+ const log = createLogger('[Health]');
2
5
  import path from 'path'
3
- import { getDatabase } from '@/engine'
6
+ import { count } from '@/engine'
4
7
  import { getAllMetrics } from '@/lib/metrics-collector.js'
5
8
  import { getDatabaseStats } from '@/lib/db-monitor.js'
6
9
  import { getCurrentResources } from '@/lib/resource-monitor.js'
@@ -19,11 +22,10 @@ function getLastSyncAt() {
19
22
 
20
23
  export const GET = async (request) => {
21
24
  try {
22
- const db = getDatabase()
23
25
  const start = process.hrtime.bigint()
24
26
 
25
- db.prepare('SELECT 1').get()
26
- db.pragma('wal_checkpoint(PASSIVE)')
27
+ // busybase liveness probe (replaces the SQLite SELECT 1 + wal_checkpoint).
28
+ try { await count('user', {}); } catch { /* table may not exist yet; still alive */ }
27
29
 
28
30
  const dbLatency = Number(process.hrtime.bigint() - start) / 1000000
29
31
  const url = new URL(request.url)
@@ -48,7 +50,7 @@ export const GET = async (request) => {
48
50
  const user = await getUser()
49
51
  if (user && (user.role === 'admin' || user.role === 'partner')) {
50
52
  health.metrics = getAllMetrics()
51
- health.database.stats = getDatabaseStats()
53
+ try { health.database.stats = getDatabaseStats() } catch { health.database.stats = null }
52
54
  health.resources = getCurrentResources()
53
55
  health.alerts = getRecentAlerts(10)
54
56
  health.memory = {
@@ -68,7 +70,7 @@ export const GET = async (request) => {
68
70
  }
69
71
  )
70
72
  } catch (error) {
71
- console.error('[Health] Check failed:', error)
73
+ log.error('check failed:', { message: error.message })
72
74
  return new Response(
73
75
  JSON.stringify({
74
76
  status: 'error',
@@ -85,11 +87,10 @@ export const GET = async (request) => {
85
87
 
86
88
  export const HEAD = async (request) => {
87
89
  try {
88
- const db = getDatabase()
89
- db.prepare('SELECT 1').get()
90
+ await count('user', {})
90
91
  return new Response(null, { status: 200 })
91
92
  } catch (error) {
92
- console.error('[Health] Check failed:', error)
93
+ log.error('check failed:', { message: error.message })
93
94
  return new Response(null, { status: 503 })
94
95
  }
95
96
  }
@@ -27,7 +27,7 @@ export const GET = async (request) => {
27
27
  }
28
28
 
29
29
  if (type === 'all' || type === 'database') {
30
- data.database = getDatabaseStats()
30
+ try { data.database = getDatabaseStats() } catch { data.database = null }
31
31
  }
32
32
 
33
33
  if (type === 'all' || type === 'resources') {
package/src/cli.js CHANGED
@@ -39,7 +39,7 @@ async function main() {
39
39
  console.log('[Thatcher] Running migrations...');
40
40
  const t1 = new Thatcher({});
41
41
  await t1.init();
42
- console.log(' Migrations complete');
42
+ console.log('[Thatcher] Migrations complete');
43
43
  break;
44
44
 
45
45
  case 'validate':
@@ -47,11 +47,11 @@ async function main() {
47
47
  try {
48
48
  const t2 = new Thatcher({});
49
49
  await t2.init();
50
- console.log(' Configuration is valid');
50
+ console.log('[Thatcher] Configuration is valid');
51
51
  console.log(` Entities: ${t2.getAllEntities().length}`);
52
52
  console.log(` Workflows: ${Object.keys(t2.config?.workflows || {}).length}`);
53
53
  } catch (err) {
54
- console.error(' Configuration error:', err.message);
54
+ console.error('[Thatcher] Configuration error:', err.message);
55
55
  process.exit(1);
56
56
  }
57
57
  break;
@@ -200,7 +200,7 @@ thresholds:
200
200
  `;
201
201
 
202
202
  fs.writeFileSync(path.join(exampleDir, 'thatcher.config.yml'), config);
203
- console.log(`✓ Example config written to ${exampleDir}/`);
203
+ console.log(`[Thatcher] Example config written to ${exampleDir}/`);
204
204
  console.log(' cd', exampleDir);
205
205
  console.log(' thatcher start');
206
206
  }
@@ -1,20 +1,13 @@
1
- /**
2
- * Configuration loading and management
3
- * Adapted from moonlanding/src/config/
4
- */
1
+ // Adapted from moonlanding/src/config/
5
2
 
6
3
  import fs from 'fs';
7
4
  import path from 'path';
8
5
  import yaml from 'js-yaml';
9
6
 
10
- /**
11
- * Load configuration from YAML file or object
12
- */
13
7
  export async function loadConfig(configSource) {
14
8
  let config;
15
9
 
16
10
  if (typeof configSource === 'string') {
17
- // File path
18
11
  const configPath = path.resolve(process.cwd(), configSource);
19
12
  if (!fs.existsSync(configPath)) {
20
13
  throw new Error(`Configuration file not found: ${configPath}`);
@@ -22,7 +15,6 @@ export async function loadConfig(configSource) {
22
15
  const content = fs.readFileSync(configPath, 'utf-8');
23
16
  config = yaml.load(content);
24
17
  } else if (typeof configSource === 'object') {
25
- // Raw object
26
18
  config = configSource;
27
19
  } else {
28
20
  throw new Error('Config must be a file path or object');
@@ -31,9 +23,6 @@ export async function loadConfig(configSource) {
31
23
  return config;
32
24
  }
33
25
 
34
- /**
35
- * Validate required configuration sections
36
- */
37
26
  export function validateConfig(config) {
38
27
  const errors = [];
39
28
 
@@ -42,7 +31,6 @@ export function validateConfig(config) {
42
31
  return errors;
43
32
  }
44
33
 
45
- // Check required sections
46
34
  const requiredSections = ['entities', 'roles', 'permission_templates'];
47
35
  for (const section of requiredSections) {
48
36
  if (!config[section]) {
@@ -50,7 +38,6 @@ export function validateConfig(config) {
50
38
  }
51
39
  }
52
40
 
53
- // Validate roles
54
41
  if (config.roles) {
55
42
  const roleNames = Object.keys(config.roles);
56
43
  if (roleNames.length === 0) {
@@ -58,7 +45,6 @@ export function validateConfig(config) {
58
45
  }
59
46
  }
60
47
 
61
- // Validate entities
62
48
  if (config.entities) {
63
49
  for (const [entityName, entityDef] of Object.entries(config.entities)) {
64
50
  if (!entityDef.fields && !entityDef.children) {
@@ -70,9 +56,6 @@ export function validateConfig(config) {
70
56
  return errors;
71
57
  }
72
58
 
73
- /**
74
- * Get configuration with defaults applied
75
- */
76
59
  export function getConfigWithDefaults(userConfig) {
77
60
  const defaults = {
78
61
  system: {
@@ -96,9 +79,6 @@ export function getConfigWithDefaults(userConfig) {
96
79
  return deepMerge(defaults, userConfig);
97
80
  }
98
81
 
99
- /**
100
- * Deep merge utility
101
- */
102
82
  function deepMerge(target, source) {
103
83
  const result = { ...target };
104
84
  for (const key of Object.keys(source)) {
@@ -1,11 +1,3 @@
1
- /**
2
- * Core constants for Thatcher SDK
3
- * HTTP codes, status values, display limits, etc.
4
- */
5
-
6
- /**
7
- * HTTP status codes
8
- */
9
1
  export const HTTP = {
10
2
  OK: 200,
11
3
  CREATED: 201,
@@ -21,9 +13,6 @@ export const HTTP = {
21
13
  SERVICE_UNAVAILABLE: 503,
22
14
  };
23
15
 
24
- /**
25
- * Error message templates
26
- */
27
16
  export const ERROR_MESSAGES = {
28
17
  notFound: (entity = 'Resource') => `${entity} not found`,
29
18
  invalidRequest: (reason = 'Invalid request') => reason,
@@ -33,9 +22,6 @@ export const ERROR_MESSAGES = {
33
22
  },
34
23
  };
35
24
 
36
- /**
37
- * Success message templates
38
- */
39
25
  export const SUCCESS_MESSAGES = {
40
26
  created: (entity = 'Item') => `${entity} created successfully`,
41
27
  updated: (entity = 'Item') => `${entity} updated successfully`,
@@ -43,23 +29,14 @@ export const SUCCESS_MESSAGES = {
43
29
  saved: 'Changes saved successfully',
44
30
  };
45
31
 
46
- /**
47
- * Record status constants
48
- */
49
32
  export const RECORD_STATUS = {
50
33
  ACTIVE: 'active',
51
34
  DELETED: 'deleted',
52
35
  ARCHIVED: 'archived',
53
36
  };
54
37
 
55
- /**
56
- * Email status
57
- */
58
38
  export const EMAIL_STATUS = 'pending';
59
39
 
60
- /**
61
- * Display/UI constants
62
- */
63
40
  export const DISPLAY = {
64
41
  MAX_API_CALLS_HISTORY: 100,
65
42
  API_TIMEOUT_MS: 30000,
@@ -74,17 +51,11 @@ export const DISPLAY = {
74
51
  DEBOUNCE_FORM_CHANGE_MS: 500,
75
52
  };
76
53
 
77
- /**
78
- * Validation patterns
79
- */
80
54
  export const VALIDATION = {
81
55
  EMAIL_REGEX: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
82
56
  PASSWORD_MIN_LENGTH: 8,
83
57
  };
84
58
 
85
- /**
86
- * Log prefixes for structured logging
87
- */
88
59
  export const LOG_PREFIXES = {
89
60
  API: '[API]',
90
61
  DB: '[DB]',
@@ -97,9 +68,6 @@ export const LOG_PREFIXES = {
97
68
  database: '[DB]',
98
69
  };
99
70
 
100
- /**
101
- * SQL type mapping
102
- */
103
71
  export const SQL_TYPES = {
104
72
  id: 'TEXT PRIMARY KEY',
105
73
  text: 'TEXT',
@@ -116,9 +84,6 @@ export const SQL_TYPES = {
116
84
  enum: 'TEXT',
117
85
  };
118
86
 
119
- /**
120
- * Authentication scopes (Google, etc.)
121
- */
122
87
  export const AUTH_SCOPES = {
123
88
  google: [
124
89
  'https://www.googleapis.com/auth/userinfo.email',
package/src/config/env.js CHANGED
@@ -1,29 +1,15 @@
1
- /**
2
- * Environment configuration
3
- * Provides typed access to environment variables with defaults
4
- */
5
-
6
- /**
7
- * Get environment variable with optional default
8
- */
9
1
  function getEnv(key, defaultValue = null) {
10
2
  const val = process.env[key];
11
3
  if (val === undefined || val === null) return defaultValue;
12
4
  return val;
13
5
  }
14
6
 
15
- /**
16
- * Get boolean from env
17
- */
18
7
  function getEnvBool(key, defaultValue = false) {
19
8
  const val = getEnv(key);
20
9
  if (val === null) return defaultValue;
21
10
  return val === 'true' || val === '1';
22
11
  }
23
12
 
24
- /**
25
- * Get number from env
26
- */
27
13
  function getEnvInt(key, defaultValue = 0) {
28
14
  const val = getEnv(key);
29
15
  if (val === null) return defaultValue;
@@ -31,9 +17,6 @@ function getEnvInt(key, defaultValue = 0) {
31
17
  return isNaN(num) ? defaultValue : num;
32
18
  }
33
19
 
34
- /**
35
- * Configuration object builder
36
- */
37
20
  export function buildConfig(overrides = {}) {
38
21
  const config = {
39
22
  db: {
@@ -107,34 +90,21 @@ export function buildConfig(overrides = {}) {
107
90
  return config;
108
91
  }
109
92
 
110
- /**
111
- * Check if Google OAuth is configured
112
- */
113
93
  export function hasGoogleAuth(config) {
114
94
  const cfg = config?.auth?.google || buildConfig().auth.google;
115
95
  return !!(cfg.clientId && cfg.clientSecret);
116
96
  }
117
97
 
118
- /**
119
- * Check if Drive integration is configured
120
- */
121
98
  export function hasDriveConfig(config) {
122
99
  const cfg = config?.drive || buildConfig().drive;
123
100
  return !!cfg.rootFolderId;
124
101
  }
125
102
 
126
- /**
127
- * Check if email sending is configured
128
- */
129
103
  export function hasEmailConfig(config) {
130
104
  const cfg = config?.email || buildConfig().email;
131
105
  return !!(cfg.smtp.user && cfg.smtp.password);
132
106
  }
133
107
 
134
- /**
135
- * Validate environment configuration
136
- * Returns array of warnings (non-fatal)
137
- */
138
108
  export function validateEnv(config = buildConfig()) {
139
109
  const warnings = [];
140
110
 
@@ -1,14 +1,7 @@
1
- /**
2
- * Spec Helpers - Utility functions for entity specifications
3
- * Provides easy access to entity specs, navigation, and derived properties
4
- */
5
-
6
- /**
7
- * Get entity spec by name
8
- * @param {string} name - Entity name
9
- * @param {object} configEngine - Config engine instance
10
- * @returns {object|null} Entity specification or null if not found
11
- */
1
+ import { createLogger } from '../lib/logger.js';
2
+
3
+ const log = createLogger('[SpecHelpers]');
4
+
12
5
  export function getSpec(name, configEngine) {
13
6
  // Callers across the framework invoke getSpec(name) without threading the engine
14
7
  // (moon's getSpec self-resolved it). Fall back to the singleton when omitted.
@@ -26,11 +19,6 @@ export function getSpec(name, configEngine) {
26
19
  }
27
20
  }
28
21
 
29
- /**
30
- * Get all navigable entities (top-level, non-embedded)
31
- * @param {object} configEngine
32
- * @returns {Array} Sorted array of entity specs
33
- */
34
22
  export function getNavItems(configEngine) {
35
23
  try {
36
24
  const allEntities = configEngine.getAllEntities();
@@ -45,16 +33,11 @@ export function getNavItems(configEngine) {
45
33
  href: `/${s.name}`,
46
34
  }));
47
35
  } catch (error) {
48
- console.error('[spec-helpers] getNavItems error:', error.message);
36
+ log.error('getNavItems error:', { message: error.message });
49
37
  return [];
50
38
  }
51
39
  }
52
40
 
53
- /**
54
- * Get child entity definitions for a parent spec
55
- * @param {object} spec - Parent entity spec
56
- * @returns {Array} Child entity configs
57
- */
58
41
  export function getChildEntities(spec) {
59
42
  if (!spec.children) return [];
60
43
  return Object.entries(spec.children).map(([key, child]) => ({
@@ -67,57 +50,26 @@ export function getChildEntities(spec) {
67
50
  }));
68
51
  }
69
52
 
70
- /**
71
- * Get parent entity name
72
- * @param {object} spec
73
- * @returns {string|null}
74
- */
75
53
  export function getParentEntity(spec) {
76
54
  return spec.parent || null;
77
55
  }
78
56
 
79
- /**
80
- * Get default sort configuration
81
- * @param {object} spec
82
- * @returns {{field: string, dir: string}}
83
- */
84
57
  export function getDefaultSort(spec) {
85
58
  return spec.list?.defaultSort || { field: 'created_at', dir: 'desc' };
86
59
  }
87
60
 
88
- /**
89
- * Get available filters for entity
90
- * @param {object} spec
91
- * @returns {Array}
92
- */
93
61
  export function getAvailableFilters(spec) {
94
62
  return spec.list?.filters || [];
95
63
  }
96
64
 
97
- /**
98
- * Get page size for entity
99
- * @param {object} spec
100
- * @returns {number}
101
- */
102
65
  export function getPageSize(spec, systemPagination = { default_page_size: 20 }) {
103
66
  return spec.list?.pageSize || systemPagination.default_page_size;
104
67
  }
105
68
 
106
- /**
107
- * Get entity label (singular or plural)
108
- * @param {object} spec
109
- * @param {boolean} plural
110
- * @returns {string}
111
- */
112
69
  export function getEntityLabel(spec, plural = false) {
113
70
  return plural ? (spec.labelPlural || spec.label) : spec.label;
114
71
  }
115
72
 
116
- /**
117
- * Get default initial state for entity (empty record with defaults)
118
- * @param {object} spec
119
- * @returns {object}
120
- */
121
73
  export function getInitialState(spec) {
122
74
  const state = {};
123
75
  for (const [key, field] of Object.entries(spec.fields)) {
@@ -139,73 +91,32 @@ export function getInitialState(spec) {
139
91
  return state;
140
92
  }
141
93
 
142
- /**
143
- * Check if entity is embedded (used in parent contexts)
144
- * @param {object} spec
145
- * @returns {boolean}
146
- */
147
94
  export function isEmbeddedEntity(spec) {
148
95
  return spec.embedded === true;
149
96
  }
150
97
 
151
- /**
152
- * Check if entity is a top-level parent
153
- * @param {object} spec
154
- * @returns {boolean}
155
- */
156
98
  export function isParentEntity(spec) {
157
99
  return !spec.embedded && !spec.parent;
158
100
  }
159
101
 
160
- /**
161
- * Check if entity has child relationships
162
- * @param {object} spec
163
- * @returns {boolean}
164
- */
165
102
  export function hasChildRelationships(spec) {
166
103
  return !!spec.children && Object.keys(spec.children).length > 0;
167
104
  }
168
105
 
169
- /**
170
- * Get entity options (enums, picklists)
171
- * @param {object} spec
172
- * @param {string} optionKey
173
- * @returns {Array}
174
- */
175
106
  export function getOptions(spec, optionKey) {
176
107
  return spec.options?.[optionKey] || [];
177
108
  }
178
109
 
179
- /**
180
- * Get display label for an option value
181
- * @param {object} spec
182
- * @param {string} optionKey
183
- * @param {string} value
184
- * @returns {string}
185
- */
186
110
  export function getOptionLabel(spec, optionKey, value) {
187
111
  const option = getOptions(spec, optionKey).find(o => o.value === value);
188
112
  return option?.label || String(value);
189
113
  }
190
114
 
191
- /**
192
- * Get color for an option value (for UI badges)
193
- * @param {object} spec
194
- * @param {string} optionKey
195
- * @param {string} value
196
- * @returns {string}
197
- */
198
115
  export function getOptionColor(spec, optionKey, value) {
199
116
  const option = getOptions(spec, optionKey).find(o => o.value === value);
200
117
  return option?.color || 'gray';
201
118
  }
202
119
 
203
- /**
204
- * Build navigation structure for menus
205
- * @param {object} configEngine
206
- * @param {object} user - Optional user for permission filtering
207
- * @returns {Array} Navigation items
208
- */
209
120
  export function buildNavigation(configEngine, user = null) {
210
121
  try {
211
122
  const allEntities = configEngine.getAllEntities();
@@ -232,7 +143,7 @@ export function buildNavigation(configEngine, user = null) {
232
143
 
233
144
  return items;
234
145
  } catch (error) {
235
- console.error('[spec-helpers] buildNavigation error:', error.message);
146
+ log.error('buildNavigation error:', { message: error.message });
236
147
  return [];
237
148
  }
238
149
  }
package/src/engine.js ADDED
@@ -0,0 +1,23 @@
1
+ // Previously `@/engine` resolved to a non-existent module; this barrel makes it
2
+ // concrete and points the data layer at busybase-store.
3
+
4
+ // Data layer (busybase)
5
+ export {
6
+ list, get, getBy, count, listWithPagination, searchWithPagination, search,
7
+ create, update, remove, bulkCreate, getChildren, batchGetChildren,
8
+ setBusyBaseClient,
9
+ } from '@/lib/busybase-store.js';
10
+
11
+ // IDs / time
12
+ export { genId, now } from '@/lib/id-helpers.js';
13
+
14
+ // Auth helpers
15
+ export { hashPassword, verifyPassword } from '@/engine.server.js';
16
+
17
+ // Audit
18
+ export { logAction } from '@/lib/busybase-audit.js';
19
+
20
+ // Legacy SQLite shims (data layer is busybase now). busybase is schemaless so there
21
+ // is no migration step; migrate() is a no-op kept for callers that still invoke it.
22
+ export function migrate() { /* no-op: busybase creates tables lazily on first insert */ }
23
+