thatcher 1.0.4

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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +398 -0
  3. package/package.json +73 -0
  4. package/src/adapters/google-auth.js +148 -0
  5. package/src/adapters/google-drive.js +209 -0
  6. package/src/app/api/[entity]/[[...path]]/route.js +33 -0
  7. package/src/app/api/audit/dashboard/route.js +77 -0
  8. package/src/app/api/audit/logs/route.js +46 -0
  9. package/src/app/api/audit/permissions/[id]/route.js +37 -0
  10. package/src/app/api/audit/permissions/route.js +93 -0
  11. package/src/app/api/audit/permissions/stats/route.js +29 -0
  12. package/src/app/api/audit/route.js +79 -0
  13. package/src/app/api/audit/stats/route.js +18 -0
  14. package/src/app/api/auth/google/callback/route.js +94 -0
  15. package/src/app/api/auth/google/route.js +58 -0
  16. package/src/app/api/auth/login/route.js +121 -0
  17. package/src/app/api/auth/logout/route.js +52 -0
  18. package/src/app/api/auth/me/route.js +16 -0
  19. package/src/app/api/auth/mwr-bridge/route.js +77 -0
  20. package/src/app/api/auth/password-reset/route.js +65 -0
  21. package/src/app/api/cron/trigger/route.js +58 -0
  22. package/src/app/api/csrf-token/route.js +8 -0
  23. package/src/app/api/debug/config/route.js +22 -0
  24. package/src/app/api/debug/hooks/route.js +16 -0
  25. package/src/app/api/debug/plugins/route.js +20 -0
  26. package/src/app/api/debug/sqlite/route.js +21 -0
  27. package/src/app/api/debug/sync/route.js +29 -0
  28. package/src/app/api/debug/workflow/route.js +20 -0
  29. package/src/app/api/domains/[domain]/route.js +26 -0
  30. package/src/app/api/domains/route.js +21 -0
  31. package/src/app/api/email/allocate/batch/route.js +106 -0
  32. package/src/app/api/email/allocate/route.js +141 -0
  33. package/src/app/api/email/receive/route.js +158 -0
  34. package/src/app/api/email/route.js +3 -0
  35. package/src/app/api/email/send/route.js +77 -0
  36. package/src/app/api/email/unallocated/route.js +47 -0
  37. package/src/app/api/files/[id]/route.js +38 -0
  38. package/src/app/api/health/route.js +95 -0
  39. package/src/app/api/metrics/route.js +73 -0
  40. package/src/app/api/monitoring/dashboard/route.js +18 -0
  41. package/src/cli.js +243 -0
  42. package/src/config/config-loader.js +112 -0
  43. package/src/config/constants.js +127 -0
  44. package/src/config/env.js +164 -0
  45. package/src/config/spec-helpers.js +232 -0
  46. package/src/engine.server.js +212 -0
  47. package/src/index.js +368 -0
  48. package/src/lib/accessibility.js +162 -0
  49. package/src/lib/action-factory.js +34 -0
  50. package/src/lib/action-utils.js +21 -0
  51. package/src/lib/alert-manager.js +189 -0
  52. package/src/lib/api-error-wrapper.js +125 -0
  53. package/src/lib/api-helpers.js +53 -0
  54. package/src/lib/api.js +82 -0
  55. package/src/lib/audit-logger-enhanced.js +117 -0
  56. package/src/lib/audit-logger.js +193 -0
  57. package/src/lib/auth-middleware.js +102 -0
  58. package/src/lib/auth-route-helpers.js +83 -0
  59. package/src/lib/business-rules-engine.js +86 -0
  60. package/src/lib/compression.js +44 -0
  61. package/src/lib/config-field-helpers.js +91 -0
  62. package/src/lib/config-generator-engine.js +445 -0
  63. package/src/lib/config-helpers.js +120 -0
  64. package/src/lib/connection-guard.js +79 -0
  65. package/src/lib/crud-action-helpers.js +34 -0
  66. package/src/lib/crud-factory.js +83 -0
  67. package/src/lib/crud-handlers.js +244 -0
  68. package/src/lib/csrf-protection.js +63 -0
  69. package/src/lib/database-core.js +258 -0
  70. package/src/lib/database-migrations.js +96 -0
  71. package/src/lib/date-utils.js +159 -0
  72. package/src/lib/db-backup.js +97 -0
  73. package/src/lib/db-monitor.js +127 -0
  74. package/src/lib/domain-loader.js +82 -0
  75. package/src/lib/email-sender.js +100 -0
  76. package/src/lib/error-boundary.js +134 -0
  77. package/src/lib/error-handler.js +84 -0
  78. package/src/lib/error-recovery.js +190 -0
  79. package/src/lib/error-resilience.js +130 -0
  80. package/src/lib/errors.js +69 -0
  81. package/src/lib/events-engine.js +182 -0
  82. package/src/lib/field-iterator.js +50 -0
  83. package/src/lib/field-registry.js +68 -0
  84. package/src/lib/field-types.js +154 -0
  85. package/src/lib/generic-crud-handler.js +32 -0
  86. package/src/lib/health-monitor.js +134 -0
  87. package/src/lib/hook-engine.js +169 -0
  88. package/src/lib/hot-reload/cache-invalidator.js +115 -0
  89. package/src/lib/hot-reload/checkpoint.js +95 -0
  90. package/src/lib/hot-reload/debug-exposure.js +67 -0
  91. package/src/lib/hot-reload/directory-watcher.js +96 -0
  92. package/src/lib/hot-reload/index.js +50 -0
  93. package/src/lib/hot-reload/mutex.js +75 -0
  94. package/src/lib/hot-reload/promise-container.js +66 -0
  95. package/src/lib/hot-reload/route-wrapper.js +46 -0
  96. package/src/lib/hot-reload/safe-error.js +51 -0
  97. package/src/lib/hot-reload/supervisor.js +161 -0
  98. package/src/lib/hot-reload/timeout-wrapper.js +52 -0
  99. package/src/lib/http-methods-factory.js +25 -0
  100. package/src/lib/index-optimizer.js +96 -0
  101. package/src/lib/index.js +35 -0
  102. package/src/lib/list-data-transform.js +39 -0
  103. package/src/lib/log-aggregator.js +116 -0
  104. package/src/lib/logger.js +55 -0
  105. package/src/lib/metrics-collector.js +102 -0
  106. package/src/lib/minifier.js +19 -0
  107. package/src/lib/monitoring-init.js +67 -0
  108. package/src/lib/next-compat.js +80 -0
  109. package/src/lib/next-polyfills.js +135 -0
  110. package/src/lib/perf-monitor.js +91 -0
  111. package/src/lib/progress-components.js +181 -0
  112. package/src/lib/query-cache.js +126 -0
  113. package/src/lib/query-engine-write.js +221 -0
  114. package/src/lib/query-engine.js +399 -0
  115. package/src/lib/query-perf.js +117 -0
  116. package/src/lib/query-string-adapter.js +75 -0
  117. package/src/lib/realtime-server.js +67 -0
  118. package/src/lib/render-cache.js +61 -0
  119. package/src/lib/request-tracker.js +43 -0
  120. package/src/lib/resource-hints.js +29 -0
  121. package/src/lib/resource-monitor.js +117 -0
  122. package/src/lib/response-formatter.js +80 -0
  123. package/src/lib/route-helpers.js +33 -0
  124. package/src/lib/route-resolver.js +142 -0
  125. package/src/lib/safe-json.js +8 -0
  126. package/src/lib/server-bootstrap.js +71 -0
  127. package/src/lib/stage-pipeline.js +153 -0
  128. package/src/lib/state-protocol.js +171 -0
  129. package/src/lib/state-transport-client.js +169 -0
  130. package/src/lib/state-transport-reconnect.js +121 -0
  131. package/src/lib/state-transport-server.js +181 -0
  132. package/src/lib/static-server.js +97 -0
  133. package/src/lib/status-helpers.js +98 -0
  134. package/src/lib/universal-handler.js +7 -0
  135. package/src/lib/utils.js +93 -0
  136. package/src/lib/validate.js +197 -0
  137. package/src/lib/validation/business-validators.js +61 -0
  138. package/src/lib/validation/csrf.js +51 -0
  139. package/src/lib/validation/file-validators.js +34 -0
  140. package/src/lib/validation/format-validators.js +106 -0
  141. package/src/lib/validation/index.js +19 -0
  142. package/src/lib/validation/rate-limit.js +31 -0
  143. package/src/lib/validation/security-validators.js +78 -0
  144. package/src/lib/validation-middleware.js +133 -0
  145. package/src/lib/validators.js +105 -0
  146. package/src/lib/with-audit-logging.js +63 -0
  147. package/src/lib/with-error-handler.js +31 -0
  148. package/src/lib/workflow-engine.js +250 -0
  149. package/src/server/server.js +305 -0
  150. package/src/services/collaborator-role.service.js +205 -0
  151. package/src/services/email-sender.js +105 -0
  152. package/src/services/notification-engine.js +110 -0
  153. package/src/services/permission.service.js +181 -0
  154. package/src/ui/advanced-search-renderer.js +42 -0
  155. package/src/ui/advanced-widgets.js +47 -0
  156. package/src/ui/auth-pages.js +114 -0
  157. package/src/ui/auth-styles.js +53 -0
  158. package/src/ui/client.js +99 -0
  159. package/src/ui/collaboration-dialogs.js +31 -0
  160. package/src/ui/common-handlers.js +156 -0
  161. package/src/ui/component-engine.js +103 -0
  162. package/src/ui/dashboard-renderer.js +150 -0
  163. package/src/ui/dialog-engine.js +147 -0
  164. package/src/ui/dialog-factory.js +38 -0
  165. package/src/ui/engagement-cards.js +76 -0
  166. package/src/ui/engagement-dialogs.js +100 -0
  167. package/src/ui/engagement-grid-renderer.js +109 -0
  168. package/src/ui/entity-renderer.js +176 -0
  169. package/src/ui/event-delegation.js +108 -0
  170. package/src/ui/fetch-json.js +24 -0
  171. package/src/ui/file-dialogs.js +81 -0
  172. package/src/ui/flexup-report-renderer.js +173 -0
  173. package/src/ui/format-helpers.js +102 -0
  174. package/src/ui/global-tags.js +144 -0
  175. package/src/ui/highlight-threading-renderer.js +176 -0
  176. package/src/ui/idle-logout.js +156 -0
  177. package/src/ui/job-management-renderer.js +61 -0
  178. package/src/ui/layout.js +219 -0
  179. package/src/ui/letter-dialogs.js +37 -0
  180. package/src/ui/ml-console-renderer.js +108 -0
  181. package/src/ui/monitoring-dashboard-client.js +136 -0
  182. package/src/ui/monitoring-dashboard.js +134 -0
  183. package/src/ui/notifications-renderer.js +51 -0
  184. package/src/ui/page-handler-admin.js +121 -0
  185. package/src/ui/page-handler-helpers.js +111 -0
  186. package/src/ui/page-handler-reviews.js +165 -0
  187. package/src/ui/page-handler-rfi.js +42 -0
  188. package/src/ui/page-handler.js +210 -0
  189. package/src/ui/password-reset-page.js +146 -0
  190. package/src/ui/perf-helpers.js +96 -0
  191. package/src/ui/perf-renderer.js +71 -0
  192. package/src/ui/permissions-ui.js +163 -0
  193. package/src/ui/picker-dialogs.js +100 -0
  194. package/src/ui/render-helpers.js +124 -0
  195. package/src/ui/renderer.js +35 -0
  196. package/src/ui/review-comparison-renderer.js +58 -0
  197. package/src/ui/review-detail-panels.js +71 -0
  198. package/src/ui/review-detail-renderer.js +170 -0
  199. package/src/ui/review-detail-script.js +95 -0
  200. package/src/ui/review-mwr-renderer.js +113 -0
  201. package/src/ui/review-renderer.js +202 -0
  202. package/src/ui/review-widgets.js +88 -0
  203. package/src/ui/review-zone-nav.js +12 -0
  204. package/src/ui/rfi-detail-renderer.js +191 -0
  205. package/src/ui/rfi-renderer.js +194 -0
  206. package/src/ui/rfi-report-renderer.js +56 -0
  207. package/src/ui/rippleui.css +1 -0
  208. package/src/ui/settings-renderer-advanced.js +195 -0
  209. package/src/ui/settings-renderer-advanced2.js +158 -0
  210. package/src/ui/settings-renderer-teams.js +112 -0
  211. package/src/ui/settings-renderer.js +166 -0
  212. package/src/ui/spacing-system.js +155 -0
  213. package/src/ui/standalone-login.js +109 -0
  214. package/src/ui/styles.css +2530 -0
  215. package/src/ui/styles2.css +1602 -0
  216. package/src/ui/test-page.js +23 -0
  217. package/src/ui/validation-rules.js +73 -0
  218. package/src/ui/validation-ui.js +147 -0
  219. package/src/ui/virtual-scroll.js +107 -0
  220. package/src/ui/webjsx.js +61 -0
  221. package/src/ui/widgets.js +152 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AnEntrypoint
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,398 @@
1
+ # Thatcher SDK
2
+
3
+ **Configuration-Driven Application Framework for Data-Intensive Web Apps**
4
+
5
+ Thatcher is a complete extraction of the moonlanding application framework. Build full-featured CRUD applications with workflows, permissions, and external integrations — all through YAML configuration, without writing code.
6
+
7
+ ## Features
8
+
9
+ - **Zero-Code CRUD** — Define entities in YAML; automatic REST API, UI, and database schema
10
+ - **Workflow Engine** — State machine with transitions, locks, and permissions
11
+ - **Role-Based Access Control** — Fine-grained permission templates per entity
12
+ - **Plugin System** — Extend entities with hooks, validators, and custom fields
13
+ - **External Integrations** — Google Drive/Gmail, Email (SMTP), PDF generation
14
+ - **Hot Reload** — Config changes reflect instantly without restart
15
+ - **Production-Ready** — SQLite, WAL mode, connection pooling, metrics, audit logging
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # 1. Install globally (or use bun x)
21
+ npm install -g thatcher
22
+
23
+ # 2. Generate starter config
24
+ thatcher example
25
+
26
+ # 3. Start the server
27
+ thatcher start
28
+ ```
29
+
30
+ Or use directly with Bun:
31
+
32
+ ```bash
33
+ bun x thatcher start
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ Thatcher is driven by a single `thatcher.config.yml` file:
39
+
40
+ ```yaml
41
+ # thatcher.config.yml
42
+ roles:
43
+ admin:
44
+ hierarchy: 0
45
+ label: Admin
46
+ permissions_scope: global
47
+ user:
48
+ hierarchy: 1
49
+ label: User
50
+ permissions_scope: assigned
51
+
52
+ permission_templates:
53
+ basic:
54
+ admin: [list, view, create, edit, delete, manage_settings]
55
+ user: [list, view]
56
+
57
+ entities:
58
+ item:
59
+ label: Item
60
+ label_plural: Items
61
+ fields:
62
+ name:
63
+ type: text
64
+ required: true
65
+ description:
66
+ type: textarea
67
+ status:
68
+ type: enum
69
+ options: [active, archived]
70
+ default: active
71
+
72
+ workflows:
73
+ simple:
74
+ stages:
75
+ - draft
76
+ - active
77
+ - completed
78
+
79
+ thresholds:
80
+ system:
81
+ pagination:
82
+ default_page_size: 20
83
+ max_page_size: 100
84
+ ```
85
+
86
+ That's it. Start the server and you have:
87
+ - SQLite tables `item` with correct schema
88
+ - REST endpoints: `GET/POST/PUT/DELETE /api/item`
89
+ - Role-based permissions enforced
90
+ - State transitions via `POST /api/item/:id/transition`
91
+
92
+ ## Programmatic Usage
93
+
94
+ ```javascript
95
+ import { createThatcher } from 'thatcher';
96
+
97
+ const thatcher = createThatcher({
98
+ config: './thatcher.config.yml',
99
+ databasePath: './data/app.db',
100
+ });
101
+
102
+ // Initialize (loads config, migrates DB, registers plugins)
103
+ await thatcher.init();
104
+
105
+ // Start HTTP server
106
+ await thatcher.startServer({ port: 3000 });
107
+
108
+ // Use APIs directly
109
+ const items = await thatcher.list('item');
110
+ const item = await thatcher.get('item', 'id123');
111
+ const created = await thatcher.create('item', { name: 'Test' }, { id: 'user1' });
112
+
113
+ // Workflow transitions
114
+ await thatcher.transition('item', item.id, 'simple', 'active', { id: 'user1', role: 'admin' });
115
+
116
+ // Permissions
117
+ const canEdit = await thatcher.can(user, thatcher.getEntitySpec('item'), 'edit');
118
+ ```
119
+
120
+ ## CLI Commands
121
+
122
+ | Command | Description |
123
+ |---------|-------------|
124
+ | `thatcher start` | Start server in production mode |
125
+ | `thatcher dev` | Start with hot reload enabled |
126
+ | `thatcher migrate` | Run database migrations only |
127
+ | `thatcher validate` | Validate configuration file |
128
+ | `thatcher console` | Open REPL with thatcher API |
129
+ | `thatcher example` | Generate example config |
130
+
131
+ ## Architecture
132
+
133
+ ### Core Components
134
+
135
+ ```
136
+ thatcher/
137
+ ├── src/
138
+ │ ├── index.js # Main entry: createThatcher()
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
+ │ │ ├── database-core.js # SQLite init, migrations
147
+ │ │ ├── query-engine.js # Read operations (GET, search)
148
+ │ │ ├── query-engine-write.js # Write operations (CRUD)
149
+ │ │ ├── config-generator-engine.js # Spec builder
150
+ │ │ ├── hook-engine.js # Event system
151
+ │ │ ├── workflow-engine.js # State machines
152
+ │ │ ├── auth-middleware.js # Auth checks
153
+ │ │ ├── crud-factory.js # Handler factory
154
+ │ │ ├── crud-handlers.js # HTTP handlers
155
+ │ │ ├── validate.js # Validation
156
+ │ │ └── logger.js # Structured logging
157
+ │ ├── services/
158
+ │ │ └── permission.service.js # Authorization
159
+ │ ├── adapters/
160
+ │ │ ├── google-auth.js # Google OAuth
161
+ │ │ └── google-drive.js # Drive file operations
162
+ │ ├── plugins/
163
+ │ │ └── index.js # Plugin auto-discovery
164
+ │ └── server/
165
+ │ └── server.js # HTTP server
166
+ └── package.json
167
+ ```
168
+
169
+ ### How It Works
170
+
171
+ 1. **Configuration Load** — `master-config.yml` parsed into memory
172
+ 2. **Spec Generation** — For each entity, ConfigEngine builds full spec from base + overrides + plugins
173
+ 3. **Database Migration** — Tables created/updated from spec fields (idempotent)
174
+ 4. **Plugin Registration** — `.plugin.js` files extend entity behavior
175
+ 5. **Request Handling** — Generic CRUD handlers enforce permissions, validate, execute hooks
176
+
177
+ ### Entity Specification
178
+
179
+ Each entity derives from config:
180
+
181
+ | Config Key | Purpose |
182
+ |-----------|---------|
183
+ | `fields` | Column definitions (type, required, ref, enum, etc.) |
184
+ | `permission_template` | Maps roles → allowed actions |
185
+ | `workflow` | State machine name for lifecycle |
186
+ | `row_access` | Scoping: `team`, `assigned`, `client` |
187
+ | `list.defaultSort` | Default list ordering |
188
+ | `has_*` flags | UI features (PDF, collaboration, notifications) |
189
+
190
+ ### Permissions
191
+
192
+ Permission templates define role capabilities per entity:
193
+
194
+ ```yaml
195
+ permission_templates:
196
+ standard:
197
+ admin: [list, view, create, edit, delete, export]
198
+ user: [list, view]
199
+ ```
200
+
201
+ Actions: `list`, `view`, `create`, `edit`, `delete`, `archive`, `export`, `manage_settings`, etc.
202
+
203
+ Row access controls which records a user can see:
204
+ - `team` — Only records in user's team
205
+ - `assigned` — Only records assigned to user
206
+ - `client` — Only records for user's client
207
+ - `assigned_or_team` — Either condition
208
+
209
+ ### Workflows
210
+
211
+ State machines with transitions:
212
+
213
+ ```yaml
214
+ workflows:
215
+ engagement_lifecycle:
216
+ state_field: stage
217
+ stages:
218
+ - name: draft
219
+ label: Draft
220
+ forward: [review, submitted]
221
+ readonly: true
222
+ - name: review
223
+ label: In Review
224
+ forward: [approved, rejected]
225
+ backward: [draft]
226
+ requires_role: [manager, partner]
227
+ - name: closed
228
+ label: Closed
229
+ entry: partner_only
230
+ ```
231
+
232
+ Transitions validated automatically:
233
+ - Only allowed transitions
234
+ - Role requirements
235
+ - Lockout period (configurable)
236
+ - Readonly state blocks
237
+
238
+ ### Hooks
239
+
240
+ Listen to lifecycle events:
241
+
242
+ ```javascript
243
+ // my-entity.plugin.js
244
+ export default {
245
+ entityName: 'item',
246
+ hooks: [
247
+ {
248
+ event: 'create:item:after',
249
+ handler: async ({ entity, id, data, user }) => {
250
+ console.log(`Item ${id} created by ${user.id}`);
251
+ // Send notification, sync external system, etc.
252
+ },
253
+ },
254
+ ],
255
+ };
256
+ ```
257
+
258
+ Hook naming: `<timing>:<entity>:<phase>`
259
+ - `create:entity:before` / `after`
260
+ - `update:entity:before` / `after`
261
+ - `delete:entity:before` / `after`
262
+ - `transition:entity:before` / `after`
263
+ - Custom: `upload_files:entity:after`, `resolve_highlight:review:after`
264
+
265
+ ### Plugins
266
+
267
+ Extend entities with custom fields and behavior:
268
+
269
+ ```javascript
270
+ // plugins/custom-item.plugin.js
271
+ export default {
272
+ entityName: 'item',
273
+ fields: {
274
+ custom_field: {
275
+ type: 'text',
276
+ label: 'Custom Field',
277
+ required: false,
278
+ },
279
+ },
280
+ validators: {
281
+ validateCustom: (data) => {
282
+ if (data.custom_field && !data.custom_field.startsWith('X')) {
283
+ return 'Custom field must start with X';
284
+ }
285
+ return null;
286
+ },
287
+ },
288
+ };
289
+ ```
290
+
291
+ All `.plugin.js` files in `plugins/` directory auto-loaded on startup.
292
+
293
+ ## External Integrations
294
+
295
+ ### Google OAuth & Drive
296
+
297
+ Set env vars:
298
+
299
+ ```bash
300
+ GOOGLE_CLIENT_ID=...
301
+ GOOGLE_CLIENT_SECRET=...
302
+ GOOGLE_SERVICE_ACCOUNT_PATH=./service-account.json
303
+ GOOGLE_DRIVE_FOLDER_ID=...
304
+ ```
305
+
306
+ Drive operations via `@/adapters/google-drive`:
307
+
308
+ ```javascript
309
+ import { uploadFile, downloadFile, exportToPdf } from 'thatcher/adapters/google-drive';
310
+
311
+ await uploadFile('./local.pdf', 'Document.pdf', { folderId: '...' });
312
+ const pdf = await exportToPdf('docId');
313
+ ```
314
+
315
+ ### Email (SMTP)
316
+
317
+ ```bash
318
+ EMAIL_HOST=smtp.gmail.com
319
+ EMAIL_PORT=587
320
+ EMAIL_USER=...
321
+ EMAIL_PASSWORD=...
322
+ EMAIL_FROM=noreply@example.com
323
+ ```
324
+
325
+ Use via `@/lib/email-sender` (included).
326
+
327
+ ## API Reference
328
+
329
+ All APIs accessible via thatcher instance:
330
+
331
+ ### CRUD
332
+ - `thatcher.list(entity, where, opts)` → Array
333
+ - `thatcher.get(entity, id)` → object
334
+ - `thatcher.create(entity, data, user)` → object
335
+ - `thatcher.update(entity, id, data, user)` → object
336
+ - `thatcher.delete(entity, id)` → void
337
+
338
+ ### Search
339
+ - `thatcher.search(entity, query, where, opts)` → Array (uses FTS)
340
+
341
+ ### Workflow
342
+ - `thatcher.transition(entityType, entityId, workflowName, toState, user, reason)` → object
343
+ - `thatcher.getAvailableTransitions(workflowName, currentState, user, record)` → Array
344
+
345
+ ### AuthZ
346
+ - `thatcher.can(user, spec, action)` → boolean
347
+ - `thatcher.requirePermission(user, spec, action)` → throws if denied
348
+
349
+ ### Config
350
+ - `thatcher.getConfigEngine()` → ConfigGeneratorEngine
351
+ - `thatcher.getEntitySpec(entityName)` → spec object
352
+ - `thatcher.getAllEntities()` → Array<string>
353
+
354
+ ### Direct DB
355
+ - `thatcher.withTransaction(cb)` → Promise<result>
356
+
357
+ ## Environment Variables
358
+
359
+ | Variable | Purpose | Default |
360
+ |----------|---------|---------|
361
+ | `PORT` | Server port | 3000 |
362
+ | `DATABASE_PATH` | SQLite file path | `./data/app.db` |
363
+ | `NODE_ENV` | `development` \| `production` | `development` |
364
+ | `DEBUG` | Enable debug logging | `false` |
365
+ | `GOOGLE_CLIENT_ID` | OAuth client ID | — |
366
+ | `GOOGLE_CLIENT_SECRET` | OAuth client secret | — |
367
+ | `GOOGLE_DRIVE_FOLDER_ID` | Root Drive folder | — |
368
+ | `EMAIL_HOST` | SMTP host | `smtp.gmail.com` |
369
+ | `EMAIL_PORT` | SMTP port | `587` |
370
+ | `EMAIL_USER` | SMTP username | — |
371
+ | `EMAIL_PASSWORD` | SMTP password | — |
372
+
373
+ ## Migrating from Moonlanding
374
+
375
+ Thatcher is a drop-in replacement for moonlanding's core:
376
+
377
+ 1. Copy `master-config.yml` → `thatcher.config.yml`
378
+ 2. Move any `.plugin.js` files to new project's `plugins/`
379
+ 3. Change import paths: `@/lib/...` → `thatcher/...`
380
+ 4. Replace `import ... from '@/engine'` with `thatcher.*` methods
381
+ 5. Update server initialization: `new Thatcher({ config: '...' })`
382
+
383
+ All business logic remains in config and plugins — no code changes required.
384
+
385
+ ## Publishing
386
+
387
+ Thatcher is published to npm as `thatcher`. Every push to `main` triggers:
388
+
389
+ 1. Bump version (from git tags or conventional commits)
390
+ 2. Build & test
391
+ 3. Publish to npm registry
392
+ 4. Create GitHub release
393
+
394
+ CI/CD ready via included GitHub Actions workflow.
395
+
396
+ ## License
397
+
398
+ MIT — Extracted from moonlanding with all functionality preserved.
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "thatcher",
3
+ "version": "1.0.4",
4
+ "description": "A config-driven application framework for building data-intensive web apps without code. 100% feature-complete extraction from moonlanding.",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "thatcher": "src/cli.js",
9
+ "thatcher-dev": "src/cli.js"
10
+ },
11
+ "scripts": {
12
+ "dev": "bun run src/cli.js dev",
13
+ "start": "bun run src/cli.js start",
14
+ "test": "bun test",
15
+ "lint": "eslint src/",
16
+ "typecheck": "tsc --noEmit"
17
+ },
18
+ "keywords": [
19
+ "framework",
20
+ "cms",
21
+ "low-code",
22
+ "config-driven",
23
+ "crud",
24
+ "workflow",
25
+ "sdk",
26
+ "bun",
27
+ "sqlite"
28
+ ],
29
+ "author": "Extracted from moonlanding",
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=18.0.0",
33
+ "bun": ">=1.0.0"
34
+ },
35
+ "dependencies": {
36
+ "@casl/ability": "^6.8.0",
37
+ "@lucia-auth/adapter-sqlite": "^3.0.2",
38
+ "arctic": "^1.9.2",
39
+ "bcrypt": "^5.1.1",
40
+ "better-sqlite3": "^11.10.0",
41
+ "compression": "^1.8.1",
42
+ "googleapis": "^140.0.0",
43
+ "js-yaml": "^4.1.1",
44
+ "lucia": "^3.2.2",
45
+ "nanoid": "^5.0.7",
46
+ "nodemailer": "^6.9.7",
47
+ "puppeteer": "^21.6.1",
48
+ "webjsx": "^0.0.73",
49
+ "tsx": "^4.7.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/better-sqlite3": "^7.6.11",
53
+ "@types/node": "^20.16.5",
54
+ "eslint": "^8.56.0",
55
+ "typescript": "^5.3.3"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/AnEntrypoint/thatcher.git"
60
+ },
61
+ "bugs": {
62
+ "url": "https://github.com/AnEntrypoint/thatcher/issues"
63
+ },
64
+ "homepage": "https://github.com/AnEntrypoint/thatcher#readme",
65
+ "files": [
66
+ "src/**/*",
67
+ "README.md",
68
+ "LICENSE"
69
+ ],
70
+ "publishConfig": {
71
+ "access": "public"
72
+ }
73
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Google Auth Adapter - Factory for Google OAuth2 clients
3
+ * Provides JWT and OAuth2 clients for Google APIs
4
+ */
5
+
6
+ import { google } from 'googleapis';
7
+ import { buildConfig } from '../config/env.js';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+
11
+ let _oauth2Client = null;
12
+ let _jwtClient = null;
13
+
14
+ /**
15
+ * Initialize Google auth clients
16
+ * @param {object} [config] - Optional config override
17
+ */
18
+ export function initGoogleAuth(config = null) {
19
+ const cfg = config || buildConfig();
20
+ const scopes = [
21
+ 'https://www.googleapis.com/auth/userinfo.email',
22
+ 'https://www.googleapis.com/auth/userinfo.profile',
23
+ 'https://www.googleapis.com/auth/drive',
24
+ 'https://www.googleapis.com/auth/gmail.send',
25
+ ];
26
+
27
+ // OAuth2 client for web flow
28
+ _oauth2Client = new google.auth.OAuth2(
29
+ cfg.auth.google.clientId,
30
+ cfg.auth.google.clientSecret,
31
+ cfg.auth.google.redirectUri
32
+ );
33
+
34
+ // JWT client for service account (server-to-server)
35
+ const serviceAccountPath = cfg.auth.google.serviceAccountPath || './config/service-account.json';
36
+ if (fs.existsSync(serviceAccountPath)) {
37
+ try {
38
+ const key = JSON.parse(fs.readFileSync(serviceAccountPath, 'utf-8'));
39
+ _jwtClient = new google.auth.JWT({
40
+ email: key.client_email,
41
+ key: key.private_key,
42
+ scopes: [
43
+ 'https://www.googleapis.com/auth/drive',
44
+ 'https://www.googleapis.com/auth/gmail.send',
45
+ ],
46
+ });
47
+ } catch (err) {
48
+ console.warn('[GoogleAuth] Failed to load service account:', err.message);
49
+ }
50
+ }
51
+
52
+ return { oauth2: _oauth2Client, jwt: _jwtClient };
53
+ }
54
+
55
+ /**
56
+ * Get OAuth2 client
57
+ * @returns {OAuth2Client|null}
58
+ */
59
+ export function getOAuth2Client() {
60
+ if (!_oauth2Client) initGoogleAuth();
61
+ return _oauth2Client;
62
+ }
63
+
64
+ /**
65
+ * Get JWT client (service account)
66
+ * @returns {JWT|null}
67
+ */
68
+ export function getJWTClient() {
69
+ if (!_jwtClient) initGoogleAuth();
70
+ return _jwtClient;
71
+ }
72
+
73
+ /**
74
+ * Generate OAuth2 authorization URL
75
+ * @param {object} options - state, access_type, prompt, etc.
76
+ * @returns {string}
77
+ */
78
+ export function generateAuthUrl(options = {}) {
79
+ const client = getOAuth2Client();
80
+ if (!client) throw new Error('Google OAuth not configured');
81
+
82
+ const defaultScopes = [
83
+ 'https://www.googleapis.com/auth/userinfo.email',
84
+ 'https://www.googleapis.com/auth/userinfo.profile',
85
+ ];
86
+
87
+ return client.generateAuthUrl({
88
+ access_type: 'offline',
89
+ prompt: 'consent',
90
+ scope: defaultScopes,
91
+ ...options,
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Exchange code for tokens
97
+ * @param {string} code
98
+ * @returns {Promise<object>} tokens
99
+ */
100
+ export async function exchangeCode(code) {
101
+ const client = getOAuth2Client();
102
+ if (!client) throw new Error('Google OAuth not configured');
103
+
104
+ const { tokens } = await client.getToken(code);
105
+ return tokens;
106
+ }
107
+
108
+ /**
109
+ * Get user info from Google
110
+ * @param {string} accessToken
111
+ * @returns {Promise<object>}
112
+ */
113
+ export async function getUserInfo(accessToken) {
114
+ const oauth2 = google.oauth2({ version: 'v2', auth: `Bearer ${accessToken}` });
115
+ const res = await oauth2.userinfo.get();
116
+ return res.data;
117
+ }
118
+
119
+ /**
120
+ * Verify ID token
121
+ * @param {string} idToken
122
+ * @returns {Promise<object>}
123
+ */
124
+ export async function verifyIdToken(idToken) {
125
+ const client = getOAuth2Client();
126
+ if (!client) throw new Error('Google OAuth not configured');
127
+
128
+ const ticket = await client.verifyIdToken({
129
+ idToken,
130
+ audience: null, // validate against any client ID (for multi-tenant)
131
+ });
132
+ return ticket.getPayload();
133
+ }
134
+
135
+ /**
136
+ * Create OAuth2 client with refresh token
137
+ * @param {string} refreshToken
138
+ * @returns {OAuth2Client}
139
+ */
140
+ export function createClientWithRefresh(refreshToken) {
141
+ const client = new google.auth.OAuth2(
142
+ buildConfig().auth.google.clientId,
143
+ buildConfig().auth.google.clientSecret,
144
+ buildConfig().auth.google.redirectUri
145
+ );
146
+ client.setCredentials({ refresh_token: refreshToken });
147
+ return client;
148
+ }