@l4yercak3/cli 1.3.2 → 2.0.0-alpha.0

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 (357) hide show
  1. package/README.md +10 -220
  2. package/dist/api/client.d.ts +12 -0
  3. package/dist/api/client.d.ts.map +1 -0
  4. package/dist/api/client.js +37 -0
  5. package/dist/api/client.js.map +1 -0
  6. package/dist/api/platform.d.ts +161 -0
  7. package/dist/api/platform.d.ts.map +1 -0
  8. package/dist/api/platform.js +70 -0
  9. package/dist/api/platform.js.map +1 -0
  10. package/dist/bin/sevenlayers.d.ts +3 -0
  11. package/dist/bin/sevenlayers.d.ts.map +1 -0
  12. package/dist/bin/sevenlayers.js +198 -0
  13. package/dist/bin/sevenlayers.js.map +1 -0
  14. package/dist/commands/agent/catalog.d.ts +5 -0
  15. package/dist/commands/agent/catalog.d.ts.map +1 -0
  16. package/dist/commands/agent/catalog.js +142 -0
  17. package/dist/commands/agent/catalog.js.map +1 -0
  18. package/dist/commands/agent/drift.d.ts +5 -0
  19. package/dist/commands/agent/drift.d.ts.map +1 -0
  20. package/dist/commands/agent/drift.js +113 -0
  21. package/dist/commands/agent/drift.js.map +1 -0
  22. package/dist/commands/agent/init.d.ts +5 -0
  23. package/dist/commands/agent/init.d.ts.map +1 -0
  24. package/dist/commands/agent/init.js +75 -0
  25. package/dist/commands/agent/init.js.map +1 -0
  26. package/dist/commands/agent/permissions.d.ts +5 -0
  27. package/dist/commands/agent/permissions.d.ts.map +1 -0
  28. package/dist/commands/agent/permissions.js +88 -0
  29. package/dist/commands/agent/permissions.js.map +1 -0
  30. package/dist/commands/agent/runner.d.ts +14 -0
  31. package/dist/commands/agent/runner.d.ts.map +1 -0
  32. package/dist/commands/agent/runner.js +59 -0
  33. package/dist/commands/agent/runner.js.map +1 -0
  34. package/dist/commands/agent/shared.d.ts +13 -0
  35. package/dist/commands/agent/shared.d.ts.map +1 -0
  36. package/dist/commands/agent/shared.js +31 -0
  37. package/dist/commands/agent/shared.js.map +1 -0
  38. package/dist/commands/agent/template.d.ts +5 -0
  39. package/dist/commands/agent/template.d.ts.map +1 -0
  40. package/dist/commands/agent/template.js +104 -0
  41. package/dist/commands/agent/template.js.map +1 -0
  42. package/dist/commands/app/connect.d.ts +7 -0
  43. package/dist/commands/app/connect.d.ts.map +1 -0
  44. package/dist/commands/app/connect.js +12 -0
  45. package/dist/commands/app/connect.js.map +1 -0
  46. package/dist/commands/app/init.d.ts +7 -0
  47. package/dist/commands/app/init.d.ts.map +1 -0
  48. package/dist/commands/app/init.js +12 -0
  49. package/dist/commands/app/init.js.map +1 -0
  50. package/dist/commands/app/link.d.ts +3 -0
  51. package/dist/commands/app/link.d.ts.map +1 -0
  52. package/dist/commands/app/link.js +92 -0
  53. package/dist/commands/app/link.js.map +1 -0
  54. package/dist/commands/app/pages.d.ts +15 -0
  55. package/dist/commands/app/pages.d.ts.map +1 -0
  56. package/dist/commands/app/pages.js +180 -0
  57. package/dist/commands/app/pages.js.map +1 -0
  58. package/dist/commands/app/register.d.ts +3 -0
  59. package/dist/commands/app/register.d.ts.map +1 -0
  60. package/dist/commands/app/register.js +120 -0
  61. package/dist/commands/app/register.js.map +1 -0
  62. package/dist/commands/app/remote.d.ts +14 -0
  63. package/dist/commands/app/remote.d.ts.map +1 -0
  64. package/dist/commands/app/remote.js +44 -0
  65. package/dist/commands/app/remote.js.map +1 -0
  66. package/dist/commands/app/setup.d.ts +3 -0
  67. package/dist/commands/app/setup.d.ts.map +1 -0
  68. package/dist/commands/app/setup.js +299 -0
  69. package/dist/commands/app/setup.js.map +1 -0
  70. package/dist/commands/app/shared.d.ts +9 -0
  71. package/dist/commands/app/shared.d.ts.map +1 -0
  72. package/dist/commands/app/shared.js +122 -0
  73. package/dist/commands/app/shared.js.map +1 -0
  74. package/dist/commands/app/sync.d.ts +7 -0
  75. package/dist/commands/app/sync.d.ts.map +1 -0
  76. package/dist/commands/app/sync.js +107 -0
  77. package/dist/commands/app/sync.js.map +1 -0
  78. package/dist/commands/booking/check.d.ts +3 -0
  79. package/dist/commands/booking/check.d.ts.map +1 -0
  80. package/dist/commands/booking/check.js +68 -0
  81. package/dist/commands/booking/check.js.map +1 -0
  82. package/dist/commands/booking/setup.d.ts +3 -0
  83. package/dist/commands/booking/setup.d.ts.map +1 -0
  84. package/dist/commands/booking/setup.js +95 -0
  85. package/dist/commands/booking/setup.js.map +1 -0
  86. package/dist/commands/booking/shared.d.ts +31 -0
  87. package/dist/commands/booking/shared.d.ts.map +1 -0
  88. package/dist/commands/booking/shared.js +112 -0
  89. package/dist/commands/booking/shared.js.map +1 -0
  90. package/dist/commands/booking/smoke.d.ts +3 -0
  91. package/dist/commands/booking/smoke.d.ts.map +1 -0
  92. package/dist/commands/booking/smoke.js +101 -0
  93. package/dist/commands/booking/smoke.js.map +1 -0
  94. package/dist/commands/cms/bind.d.ts +3 -0
  95. package/dist/commands/cms/bind.d.ts.map +1 -0
  96. package/dist/commands/cms/bind.js +212 -0
  97. package/dist/commands/cms/bind.js.map +1 -0
  98. package/dist/commands/cms/content.d.ts +40 -0
  99. package/dist/commands/cms/content.d.ts.map +1 -0
  100. package/dist/commands/cms/content.js +169 -0
  101. package/dist/commands/cms/content.js.map +1 -0
  102. package/dist/commands/cms/doctor.d.ts +3 -0
  103. package/dist/commands/cms/doctor.d.ts.map +1 -0
  104. package/dist/commands/cms/doctor.js +69 -0
  105. package/dist/commands/cms/doctor.js.map +1 -0
  106. package/dist/commands/cms/migrate.d.ts +3 -0
  107. package/dist/commands/cms/migrate.d.ts.map +1 -0
  108. package/dist/commands/cms/migrate.js +78 -0
  109. package/dist/commands/cms/migrate.js.map +1 -0
  110. package/dist/commands/cms/registry.d.ts +3 -0
  111. package/dist/commands/cms/registry.d.ts.map +1 -0
  112. package/dist/commands/cms/registry.js +161 -0
  113. package/dist/commands/cms/registry.js.map +1 -0
  114. package/dist/commands/cms/seed.d.ts +3 -0
  115. package/dist/commands/cms/seed.d.ts.map +1 -0
  116. package/dist/commands/cms/seed.js +102 -0
  117. package/dist/commands/cms/seed.js.map +1 -0
  118. package/dist/commands/cms/shared.d.ts +22 -0
  119. package/dist/commands/cms/shared.d.ts.map +1 -0
  120. package/dist/commands/cms/shared.js +82 -0
  121. package/dist/commands/cms/shared.js.map +1 -0
  122. package/dist/commands/doctor/target.d.ts +3 -0
  123. package/dist/commands/doctor/target.d.ts.map +1 -0
  124. package/dist/commands/doctor/target.js +46 -0
  125. package/dist/commands/doctor/target.js.map +1 -0
  126. package/dist/commands/env/list.d.ts +3 -0
  127. package/dist/commands/env/list.d.ts.map +1 -0
  128. package/dist/commands/env/list.js +28 -0
  129. package/dist/commands/env/list.js.map +1 -0
  130. package/dist/commands/env/set.d.ts +3 -0
  131. package/dist/commands/env/set.d.ts.map +1 -0
  132. package/dist/commands/env/set.js +36 -0
  133. package/dist/commands/env/set.js.map +1 -0
  134. package/dist/commands/env/use.d.ts +3 -0
  135. package/dist/commands/env/use.d.ts.map +1 -0
  136. package/dist/commands/env/use.js +15 -0
  137. package/dist/commands/env/use.js.map +1 -0
  138. package/dist/commands/legacy/connect.d.ts +3 -0
  139. package/dist/commands/legacy/connect.d.ts.map +1 -0
  140. package/dist/commands/legacy/connect.js +8 -0
  141. package/dist/commands/legacy/connect.js.map +1 -0
  142. package/dist/commands/legacy/pages.d.ts +3 -0
  143. package/dist/commands/legacy/pages.d.ts.map +1 -0
  144. package/dist/commands/legacy/pages.js +16 -0
  145. package/dist/commands/legacy/pages.js.map +1 -0
  146. package/dist/commands/legacy/spread.d.ts +3 -0
  147. package/dist/commands/legacy/spread.d.ts.map +1 -0
  148. package/dist/commands/legacy/spread.js +8 -0
  149. package/dist/commands/legacy/spread.js.map +1 -0
  150. package/dist/commands/legacy/sync.d.ts +3 -0
  151. package/dist/commands/legacy/sync.d.ts.map +1 -0
  152. package/dist/commands/legacy/sync.js +8 -0
  153. package/dist/commands/legacy/sync.js.map +1 -0
  154. package/dist/config/env-diff.d.ts +10 -0
  155. package/dist/config/env-diff.d.ts.map +1 -0
  156. package/dist/config/env-diff.js +24 -0
  157. package/dist/config/env-diff.js.map +1 -0
  158. package/dist/config/env-parser.d.ts +20 -0
  159. package/dist/config/env-parser.d.ts.map +1 -0
  160. package/dist/config/env-parser.js +70 -0
  161. package/dist/config/env-parser.js.map +1 -0
  162. package/dist/config/env-writer.d.ts +22 -0
  163. package/dist/config/env-writer.d.ts.map +1 -0
  164. package/dist/config/env-writer.js +172 -0
  165. package/dist/config/env-writer.js.map +1 -0
  166. package/dist/config/profile-store.d.ts +29 -0
  167. package/dist/config/profile-store.d.ts.map +1 -0
  168. package/dist/config/profile-store.js +257 -0
  169. package/dist/config/profile-store.js.map +1 -0
  170. package/dist/core/args.d.ts +11 -0
  171. package/dist/core/args.d.ts.map +1 -0
  172. package/dist/core/args.js +106 -0
  173. package/dist/core/args.js.map +1 -0
  174. package/dist/core/colors.d.ts +6 -0
  175. package/dist/core/colors.d.ts.map +1 -0
  176. package/dist/core/colors.js +29 -0
  177. package/dist/core/colors.js.map +1 -0
  178. package/dist/safety/target-guard.d.ts +16 -0
  179. package/dist/safety/target-guard.d.ts.map +1 -0
  180. package/dist/safety/target-guard.js +55 -0
  181. package/dist/safety/target-guard.js.map +1 -0
  182. package/dist/testing/booking-smoke.d.ts +17 -0
  183. package/dist/testing/booking-smoke.d.ts.map +1 -0
  184. package/dist/testing/booking-smoke.js +43 -0
  185. package/dist/testing/booking-smoke.js.map +1 -0
  186. package/dist/tests/agent-commands.test.d.ts +2 -0
  187. package/dist/tests/agent-commands.test.d.ts.map +1 -0
  188. package/dist/tests/agent-commands.test.js +180 -0
  189. package/dist/tests/agent-commands.test.js.map +1 -0
  190. package/dist/tests/agent-governance.test.d.ts +2 -0
  191. package/dist/tests/agent-governance.test.d.ts.map +1 -0
  192. package/dist/tests/agent-governance.test.js +233 -0
  193. package/dist/tests/agent-governance.test.js.map +1 -0
  194. package/dist/tests/app-commands.test.d.ts +2 -0
  195. package/dist/tests/app-commands.test.d.ts.map +1 -0
  196. package/dist/tests/app-commands.test.js +462 -0
  197. package/dist/tests/app-commands.test.js.map +1 -0
  198. package/dist/tests/booking-commands.test.d.ts +2 -0
  199. package/dist/tests/booking-commands.test.d.ts.map +1 -0
  200. package/dist/tests/booking-commands.test.js +204 -0
  201. package/dist/tests/booking-commands.test.js.map +1 -0
  202. package/dist/tests/booking-smoke.test.d.ts +2 -0
  203. package/dist/tests/booking-smoke.test.d.ts.map +1 -0
  204. package/dist/tests/booking-smoke.test.js +183 -0
  205. package/dist/tests/booking-smoke.test.js.map +1 -0
  206. package/dist/tests/cms-commands.test.d.ts +2 -0
  207. package/dist/tests/cms-commands.test.d.ts.map +1 -0
  208. package/dist/tests/cms-commands.test.js +254 -0
  209. package/dist/tests/cms-commands.test.js.map +1 -0
  210. package/dist/tests/cms-ops.test.d.ts +2 -0
  211. package/dist/tests/cms-ops.test.d.ts.map +1 -0
  212. package/dist/tests/cms-ops.test.js +125 -0
  213. package/dist/tests/cms-ops.test.js.map +1 -0
  214. package/dist/tests/env-writer.test.d.ts +2 -0
  215. package/dist/tests/env-writer.test.d.ts.map +1 -0
  216. package/dist/tests/env-writer.test.js +90 -0
  217. package/dist/tests/env-writer.test.js.map +1 -0
  218. package/dist/tests/profile-store.test.d.ts +2 -0
  219. package/dist/tests/profile-store.test.d.ts.map +1 -0
  220. package/dist/tests/profile-store.test.js +88 -0
  221. package/dist/tests/profile-store.test.js.map +1 -0
  222. package/dist/tests/target-guard.test.d.ts +2 -0
  223. package/dist/tests/target-guard.test.d.ts.map +1 -0
  224. package/dist/tests/target-guard.test.js +132 -0
  225. package/dist/tests/target-guard.test.js.map +1 -0
  226. package/dist/ui/logo.d.ts +2 -0
  227. package/dist/ui/logo.d.ts.map +1 -0
  228. package/dist/ui/logo.js +22 -0
  229. package/dist/ui/logo.js.map +1 -0
  230. package/package.json +17 -53
  231. package/.claude/settings.local.json +0 -36
  232. package/.cursor/rules.md +0 -203
  233. package/.eslintrc.js +0 -31
  234. package/CLAUDE.md +0 -100
  235. package/bin/cli.js +0 -116
  236. package/docs/ADDING_FRAMEWORK_DETECTORS.md +0 -391
  237. package/docs/ADDING_NEW_PROJECT_TYPE.md +0 -156
  238. package/docs/ARCHITECTURE_RELATIONSHIPS.md +0 -411
  239. package/docs/CLI_AUTHENTICATION.md +0 -214
  240. package/docs/CLI_PAGE_DETECTION_REQUIREMENTS.md +0 -519
  241. package/docs/CRM-PIPELINES-SEQUENCES-SPEC.md +0 -429
  242. package/docs/DETECTOR_ARCHITECTURE.md +0 -326
  243. package/docs/DEVELOPMENT.md +0 -194
  244. package/docs/IMPLEMENTATION_PHASES.md +0 -468
  245. package/docs/INTEGRATION_PATHS_ARCHITECTURE.md +0 -1543
  246. package/docs/OAUTH_CLARIFICATION.md +0 -258
  247. package/docs/OAUTH_SETUP_GUIDE_TEMPLATE.md +0 -211
  248. package/docs/PHASE_0_PROGRESS.md +0 -120
  249. package/docs/PHASE_1_COMPLETE.md +0 -366
  250. package/docs/PHASE_SUMMARY.md +0 -149
  251. package/docs/PLAN.md +0 -511
  252. package/docs/README.md +0 -56
  253. package/docs/STRIPE_INTEGRATION.md +0 -447
  254. package/docs/SUMMARY.md +0 -230
  255. package/docs/UPDATED_PLAN.md +0 -447
  256. package/docs/mcp_server/MCP_EXTENSION_GUIDE.md +0 -1313
  257. package/docs/mcp_server/MCP_SERVER_ARCHITECTURE.md +0 -1481
  258. package/docs/mcp_server/applicationOntology.ts +0 -817
  259. package/docs/mcp_server/cliApplications.ts +0 -639
  260. package/docs/mcp_server/crmOntology.ts +0 -1063
  261. package/docs/mcp_server/eventOntology.ts +0 -1183
  262. package/docs/mcp_server/formsOntology.ts +0 -1401
  263. package/docs/mcp_server/ontologySchemas.ts +0 -185
  264. package/docs/mcp_server/schema.ts +0 -250
  265. package/docs/microsass_production_machine/CLI_API_REFERENCE.md +0 -1197
  266. package/docs/microsass_production_machine/CLI_PRODUCT_VISION.md +0 -676
  267. package/docs/microsass_production_machine/CLI_REQUIREMENTS.md +0 -606
  268. package/docs/microsass_production_machine/CONNECTED_APPLICATIONS_SPEC.md +0 -390
  269. package/docs/microsass_production_machine/IMPLEMENTATION_ROADMAP.md +0 -725
  270. package/docs/microsass_production_machine/OBJECT_MAPPINGS.md +0 -808
  271. package/docs/microsass_production_machine/REFERENCE_IMPLEMENTATION.md +0 -532
  272. package/src/api/backend-client.js +0 -449
  273. package/src/commands/api-keys.js +0 -119
  274. package/src/commands/connect.js +0 -253
  275. package/src/commands/login.js +0 -332
  276. package/src/commands/logout.js +0 -30
  277. package/src/commands/mcp-server.js +0 -85
  278. package/src/commands/mcp-setup.js +0 -686
  279. package/src/commands/pages.js +0 -317
  280. package/src/commands/scaffold.js +0 -409
  281. package/src/commands/spread.js +0 -861
  282. package/src/commands/status.js +0 -62
  283. package/src/commands/sync.js +0 -169
  284. package/src/commands/upgrade.js +0 -48
  285. package/src/config/config-manager.js +0 -206
  286. package/src/detectors/api-client-detector.js +0 -85
  287. package/src/detectors/base-detector.js +0 -77
  288. package/src/detectors/database-detector.js +0 -245
  289. package/src/detectors/expo-detector.js +0 -166
  290. package/src/detectors/github-detector.js +0 -74
  291. package/src/detectors/index.js +0 -106
  292. package/src/detectors/mapping-suggestor.js +0 -119
  293. package/src/detectors/model-detector.js +0 -318
  294. package/src/detectors/nextjs-detector.js +0 -139
  295. package/src/detectors/oauth-detector.js +0 -122
  296. package/src/detectors/page-detector.js +0 -480
  297. package/src/detectors/registry.js +0 -121
  298. package/src/generators/api-client-generator.js +0 -223
  299. package/src/generators/api-only/client.js +0 -683
  300. package/src/generators/api-only/index.js +0 -96
  301. package/src/generators/api-only/types.js +0 -618
  302. package/src/generators/api-only/webhooks.js +0 -377
  303. package/src/generators/env-generator.js +0 -191
  304. package/src/generators/expo-auth-generator.js +0 -1009
  305. package/src/generators/gitignore-generator.js +0 -92
  306. package/src/generators/index.js +0 -166
  307. package/src/generators/manifest-generator.js +0 -154
  308. package/src/generators/mcp-guide-generator.js +0 -256
  309. package/src/generators/nextauth-generator.js +0 -247
  310. package/src/generators/oauth-guide-generator.js +0 -277
  311. package/src/generators/quickstart/components/index.js +0 -1699
  312. package/src/generators/quickstart/components-mobile/index.js +0 -1440
  313. package/src/generators/quickstart/database/convex.js +0 -1257
  314. package/src/generators/quickstart/database/index.js +0 -34
  315. package/src/generators/quickstart/database/supabase.js +0 -1132
  316. package/src/generators/quickstart/hooks/index.js +0 -1065
  317. package/src/generators/quickstart/index.js +0 -191
  318. package/src/generators/quickstart/pages/index.js +0 -1466
  319. package/src/generators/quickstart/screens/index.js +0 -1498
  320. package/src/logo.js +0 -116
  321. package/src/mcp/auth.js +0 -127
  322. package/src/mcp/registry/domains/applications.js +0 -516
  323. package/src/mcp/registry/domains/benefits.js +0 -798
  324. package/src/mcp/registry/domains/codegen.js +0 -894
  325. package/src/mcp/registry/domains/core.js +0 -324
  326. package/src/mcp/registry/domains/crm.js +0 -591
  327. package/src/mcp/registry/domains/events.js +0 -649
  328. package/src/mcp/registry/domains/forms.js +0 -696
  329. package/src/mcp/registry/index.js +0 -164
  330. package/src/mcp/server.js +0 -116
  331. package/src/utils/file-utils.js +0 -117
  332. package/src/utils/init-helpers.js +0 -243
  333. package/src/utils/prompt-utils.js +0 -195
  334. package/templates/CLAUDE.md +0 -86
  335. package/tests/api-client-detector.test.js +0 -214
  336. package/tests/api-client-generator.test.js +0 -176
  337. package/tests/backend-client.test.js +0 -640
  338. package/tests/base-detector.test.js +0 -101
  339. package/tests/commands/login.test.js +0 -143
  340. package/tests/commands/logout.test.js +0 -84
  341. package/tests/commands/status.test.js +0 -167
  342. package/tests/config-manager.test.js +0 -321
  343. package/tests/database-detector.test.js +0 -221
  344. package/tests/detector-index.test.js +0 -209
  345. package/tests/detector-registry.test.js +0 -93
  346. package/tests/env-generator.test.js +0 -278
  347. package/tests/expo-detector.test.js +0 -263
  348. package/tests/file-utils.test.js +0 -194
  349. package/tests/generators-index.test.js +0 -454
  350. package/tests/github-detector.test.js +0 -145
  351. package/tests/gitignore-generator.test.js +0 -109
  352. package/tests/logo.test.js +0 -96
  353. package/tests/nextauth-generator.test.js +0 -255
  354. package/tests/nextjs-detector.test.js +0 -235
  355. package/tests/oauth-detector.test.js +0 -264
  356. package/tests/oauth-guide-generator.test.js +0 -273
  357. package/tests/page-detector.test.js +0 -371
@@ -1,1257 +0,0 @@
1
- /**
2
- * Convex Database Generator
3
- * Generates Convex schema, queries, and mutations for L4YERCAK3 integration
4
- */
5
-
6
- const path = require('path');
7
- const { ensureDir, writeFileWithBackup, checkFileOverwrite } = require('../../../utils/file-utils');
8
-
9
- class ConvexGenerator {
10
- /**
11
- * Generate Convex database files
12
- * @param {Object} options - Generation options
13
- * @returns {Promise<Object>} - Generated file paths
14
- */
15
- async generate(options) {
16
- const { projectPath, features = [] } = options;
17
-
18
- const results = {
19
- schema: null,
20
- objects: null,
21
- frontendUsers: null,
22
- sync: null,
23
- http: null,
24
- };
25
-
26
- const convexDir = path.join(projectPath, 'convex');
27
- ensureDir(convexDir);
28
-
29
- // Generate schema.ts
30
- results.schema = await this.generateSchema(convexDir, features);
31
-
32
- // Generate objects.ts (queries/mutations for universal objects)
33
- results.objects = await this.generateObjects(convexDir);
34
-
35
- // Generate frontendUsers.ts (auth-related queries/mutations)
36
- if (features.includes('oauth')) {
37
- results.frontendUsers = await this.generateFrontendUsers(convexDir);
38
- }
39
-
40
- // Generate sync.ts (L4YERCAK3 sync logic)
41
- results.sync = await this.generateSync(convexDir);
42
-
43
- // Generate http.ts (webhook endpoints)
44
- results.http = await this.generateHttp(convexDir);
45
-
46
- return results;
47
- }
48
-
49
- async generateSchema(convexDir, _features) {
50
- const outputPath = path.join(convexDir, 'schema.ts');
51
-
52
- const action = await checkFileOverwrite(outputPath);
53
- if (action === 'skip') {
54
- return null;
55
- }
56
-
57
- const content = `/**
58
- * Convex Schema for L4YERCAK3 Integration
59
- * Auto-generated by @l4yercak3/cli
60
- *
61
- * This schema follows the ontology-first pattern, mirroring
62
- * L4YERCAK3's universal object structure for seamless sync.
63
- */
64
-
65
- import { defineSchema, defineTable } from "convex/server";
66
- import { v } from "convex/values";
67
-
68
- export default defineSchema({
69
- // ============================================
70
- // ONTOLOGY: Universal object storage
71
- // ============================================
72
-
73
- objects: defineTable({
74
- // L4YERCAK3 sync
75
- l4yercak3Id: v.optional(v.string()),
76
- organizationId: v.string(),
77
-
78
- // Core fields (all objects have these)
79
- type: v.string(), // "contact", "event", "form", "product", "order", etc.
80
- subtype: v.optional(v.string()), // Type-specific classification
81
- name: v.string(),
82
- status: v.string(),
83
-
84
- // Type-specific data stored as JSON
85
- customProperties: v.any(),
86
-
87
- // Sync tracking
88
- syncStatus: v.union(
89
- v.literal("synced"),
90
- v.literal("pending_push"),
91
- v.literal("pending_pull"),
92
- v.literal("conflict"),
93
- v.literal("local_only")
94
- ),
95
- syncedAt: v.optional(v.number()),
96
- localVersion: v.number(),
97
- remoteVersion: v.optional(v.number()),
98
-
99
- // Timestamps
100
- createdAt: v.number(),
101
- updatedAt: v.number(),
102
- deletedAt: v.optional(v.number()), // Soft delete
103
- })
104
- .index("by_l4yercak3_id", ["l4yercak3Id"])
105
- .index("by_type", ["type"])
106
- .index("by_type_status", ["type", "status"])
107
- .index("by_type_subtype", ["type", "subtype"])
108
- .index("by_sync_status", ["syncStatus"])
109
- .index("by_organization", ["organizationId"])
110
- .index("by_updated", ["updatedAt"]),
111
-
112
- // Relationships between objects
113
- objectLinks: defineTable({
114
- l4yercak3Id: v.optional(v.string()),
115
- fromObjectId: v.id("objects"),
116
- toObjectId: v.id("objects"),
117
- linkType: v.string(), // "attendee", "sponsor", "submission", etc.
118
- metadata: v.optional(v.any()),
119
- syncStatus: v.string(),
120
- createdAt: v.number(),
121
- })
122
- .index("by_from", ["fromObjectId"])
123
- .index("by_to", ["toObjectId"])
124
- .index("by_from_type", ["fromObjectId", "linkType"])
125
- .index("by_to_type", ["toObjectId", "linkType"]),
126
-
127
- // ============================================
128
- // AUTHENTICATION: Local user management
129
- // ============================================
130
-
131
- frontendUsers: defineTable({
132
- // L4YERCAK3 sync - users become CRM contacts
133
- l4yercak3ContactId: v.optional(v.string()),
134
- l4yercak3FrontendUserId: v.optional(v.string()),
135
- organizationId: v.string(),
136
-
137
- // Core identity
138
- email: v.string(),
139
- emailVerified: v.boolean(),
140
- name: v.optional(v.string()),
141
- firstName: v.optional(v.string()),
142
- lastName: v.optional(v.string()),
143
- image: v.optional(v.string()),
144
- phone: v.optional(v.string()),
145
-
146
- // Local auth (stored securely, never synced)
147
- passwordHash: v.optional(v.string()),
148
-
149
- // OAuth accounts (stored locally)
150
- oauthAccounts: v.array(
151
- v.object({
152
- provider: v.string(),
153
- providerAccountId: v.string(),
154
- accessToken: v.optional(v.string()),
155
- refreshToken: v.optional(v.string()),
156
- expiresAt: v.optional(v.number()),
157
- scope: v.optional(v.string()),
158
- })
159
- ),
160
-
161
- // App-specific
162
- role: v.string(), // "user", "admin", "moderator"
163
- preferences: v.optional(
164
- v.object({
165
- language: v.optional(v.string()),
166
- timezone: v.optional(v.string()),
167
- theme: v.optional(v.string()),
168
- emailNotifications: v.optional(v.boolean()),
169
- })
170
- ),
171
-
172
- // Sync
173
- syncStatus: v.string(),
174
- syncedAt: v.optional(v.number()),
175
-
176
- // Timestamps
177
- createdAt: v.number(),
178
- updatedAt: v.number(),
179
- lastLoginAt: v.optional(v.number()),
180
- })
181
- .index("by_email", ["email"])
182
- .index("by_l4yercak3_contact", ["l4yercak3ContactId"])
183
- .index("by_organization", ["organizationId"]),
184
-
185
- // Sessions for auth
186
- sessions: defineTable({
187
- userId: v.id("frontendUsers"),
188
- sessionToken: v.string(),
189
- expiresAt: v.number(),
190
- userAgent: v.optional(v.string()),
191
- ipAddress: v.optional(v.string()),
192
- createdAt: v.number(),
193
- })
194
- .index("by_token", ["sessionToken"])
195
- .index("by_user", ["userId"]),
196
-
197
- // ============================================
198
- // STRIPE: Local payment handling
199
- // ============================================
200
-
201
- stripeCustomers: defineTable({
202
- frontendUserId: v.id("frontendUsers"),
203
- stripeCustomerId: v.string(),
204
- l4yercak3ContactId: v.optional(v.string()),
205
- email: v.string(),
206
- name: v.optional(v.string()),
207
- defaultPaymentMethodId: v.optional(v.string()),
208
- syncStatus: v.string(),
209
- createdAt: v.number(),
210
- })
211
- .index("by_stripe_id", ["stripeCustomerId"])
212
- .index("by_user", ["frontendUserId"]),
213
-
214
- stripePayments: defineTable({
215
- stripePaymentIntentId: v.string(),
216
- stripeCustomerId: v.optional(v.string()),
217
- frontendUserId: v.optional(v.id("frontendUsers")),
218
-
219
- // Payment details
220
- amount: v.number(),
221
- currency: v.string(),
222
- status: v.string(),
223
- paymentMethod: v.optional(v.string()),
224
-
225
- // What was purchased
226
- metadata: v.object({
227
- type: v.string(), // "event_ticket", "product", "subscription"
228
- items: v.array(
229
- v.object({
230
- objectId: v.optional(v.id("objects")),
231
- l4yercak3ProductId: v.optional(v.string()),
232
- name: v.string(),
233
- quantity: v.number(),
234
- priceInCents: v.number(),
235
- })
236
- ),
237
- eventId: v.optional(v.string()),
238
- }),
239
-
240
- // L4YERCAK3 sync
241
- l4yercak3OrderId: v.optional(v.string()),
242
- l4yercak3InvoiceId: v.optional(v.string()),
243
- syncStatus: v.string(),
244
- syncedAt: v.optional(v.number()),
245
-
246
- // Timestamps
247
- createdAt: v.number(),
248
- completedAt: v.optional(v.number()),
249
- })
250
- .index("by_stripe_id", ["stripePaymentIntentId"])
251
- .index("by_customer", ["stripeCustomerId"])
252
- .index("by_user", ["frontendUserId"])
253
- .index("by_sync_status", ["syncStatus"]),
254
-
255
- // ============================================
256
- // SYNC: Job tracking
257
- // ============================================
258
-
259
- syncJobs: defineTable({
260
- entityType: v.string(),
261
- direction: v.union(
262
- v.literal("push"),
263
- v.literal("pull"),
264
- v.literal("bidirectional")
265
- ),
266
- status: v.union(
267
- v.literal("pending"),
268
- v.literal("running"),
269
- v.literal("completed"),
270
- v.literal("failed")
271
- ),
272
-
273
- cursor: v.optional(v.string()),
274
- processedCount: v.number(),
275
- totalCount: v.optional(v.number()),
276
-
277
- errorMessage: v.optional(v.string()),
278
- errorDetails: v.optional(v.any()),
279
-
280
- startedAt: v.number(),
281
- completedAt: v.optional(v.number()),
282
- })
283
- .index("by_status", ["status"])
284
- .index("by_entity", ["entityType"]),
285
-
286
- syncConflicts: defineTable({
287
- objectId: v.id("objects"),
288
- localVersion: v.any(),
289
- remoteVersion: v.any(),
290
- conflictType: v.string(), // "update_conflict", "delete_conflict"
291
- resolvedAt: v.optional(v.number()),
292
- resolution: v.optional(v.string()), // "local_wins", "remote_wins", "merged"
293
- createdAt: v.number(),
294
- })
295
- .index("by_object", ["objectId"])
296
- .index("by_unresolved", ["resolvedAt"]),
297
- });
298
- `;
299
-
300
- return writeFileWithBackup(outputPath, content, action);
301
- }
302
-
303
- async generateObjects(convexDir) {
304
- const outputPath = path.join(convexDir, 'objects.ts');
305
-
306
- const action = await checkFileOverwrite(outputPath);
307
- if (action === 'skip') {
308
- return null;
309
- }
310
-
311
- const content = `/**
312
- * Convex Objects Queries and Mutations
313
- * Auto-generated by @l4yercak3/cli
314
- *
315
- * Universal object operations for the ontology-first pattern.
316
- */
317
-
318
- import { v } from "convex/values";
319
- import { query, mutation } from "./_generated/server";
320
-
321
- // ============ Queries ============
322
-
323
- export const list = query({
324
- args: {
325
- type: v.string(),
326
- status: v.optional(v.string()),
327
- subtype: v.optional(v.string()),
328
- limit: v.optional(v.number()),
329
- },
330
- handler: async (ctx, args) => {
331
- let q = ctx.db
332
- .query("objects")
333
- .withIndex("by_type", (q) => q.eq("type", args.type));
334
-
335
- const objects = await q.collect();
336
-
337
- // Filter by status/subtype if provided
338
- let filtered = objects.filter((o) => !o.deletedAt);
339
- if (args.status) {
340
- filtered = filtered.filter((o) => o.status === args.status);
341
- }
342
- if (args.subtype) {
343
- filtered = filtered.filter((o) => o.subtype === args.subtype);
344
- }
345
-
346
- // Sort by updatedAt descending
347
- filtered.sort((a, b) => b.updatedAt - a.updatedAt);
348
-
349
- // Apply limit
350
- if (args.limit) {
351
- filtered = filtered.slice(0, args.limit);
352
- }
353
-
354
- return filtered;
355
- },
356
- });
357
-
358
- export const get = query({
359
- args: { id: v.id("objects") },
360
- handler: async (ctx, args) => {
361
- return ctx.db.get(args.id);
362
- },
363
- });
364
-
365
- export const getByL4yercak3Id = query({
366
- args: { l4yercak3Id: v.string() },
367
- handler: async (ctx, args) => {
368
- return ctx.db
369
- .query("objects")
370
- .withIndex("by_l4yercak3_id", (q) => q.eq("l4yercak3Id", args.l4yercak3Id))
371
- .first();
372
- },
373
- });
374
-
375
- export const search = query({
376
- args: {
377
- type: v.string(),
378
- searchTerm: v.string(),
379
- },
380
- handler: async (ctx, args) => {
381
- const objects = await ctx.db
382
- .query("objects")
383
- .withIndex("by_type", (q) => q.eq("type", args.type))
384
- .collect();
385
-
386
- const term = args.searchTerm.toLowerCase();
387
- return objects.filter(
388
- (o) =>
389
- !o.deletedAt &&
390
- (o.name.toLowerCase().includes(term) ||
391
- JSON.stringify(o.customProperties).toLowerCase().includes(term))
392
- );
393
- },
394
- });
395
-
396
- // ============ Mutations ============
397
-
398
- export const create = mutation({
399
- args: {
400
- type: v.string(),
401
- subtype: v.optional(v.string()),
402
- name: v.string(),
403
- status: v.string(),
404
- customProperties: v.any(),
405
- organizationId: v.string(),
406
- l4yercak3Id: v.optional(v.string()),
407
- },
408
- handler: async (ctx, args) => {
409
- const now = Date.now();
410
-
411
- const id = await ctx.db.insert("objects", {
412
- type: args.type,
413
- subtype: args.subtype,
414
- name: args.name,
415
- status: args.status,
416
- customProperties: args.customProperties,
417
- organizationId: args.organizationId,
418
- l4yercak3Id: args.l4yercak3Id,
419
- syncStatus: args.l4yercak3Id ? "synced" : "local_only",
420
- syncedAt: args.l4yercak3Id ? now : undefined,
421
- localVersion: 1,
422
- createdAt: now,
423
- updatedAt: now,
424
- });
425
-
426
- return id;
427
- },
428
- });
429
-
430
- export const update = mutation({
431
- args: {
432
- id: v.id("objects"),
433
- name: v.optional(v.string()),
434
- status: v.optional(v.string()),
435
- subtype: v.optional(v.string()),
436
- customProperties: v.optional(v.any()),
437
- },
438
- handler: async (ctx, args) => {
439
- const existing = await ctx.db.get(args.id);
440
- if (!existing) {
441
- throw new Error("Object not found");
442
- }
443
-
444
- const updates: Record<string, unknown> = {
445
- updatedAt: Date.now(),
446
- localVersion: existing.localVersion + 1,
447
- };
448
-
449
- if (args.name !== undefined) updates.name = args.name;
450
- if (args.status !== undefined) updates.status = args.status;
451
- if (args.subtype !== undefined) updates.subtype = args.subtype;
452
- if (args.customProperties !== undefined) {
453
- updates.customProperties = {
454
- ...existing.customProperties,
455
- ...args.customProperties,
456
- };
457
- }
458
-
459
- // Mark as pending push if it was previously synced
460
- if (existing.syncStatus === "synced") {
461
- updates.syncStatus = "pending_push";
462
- }
463
-
464
- await ctx.db.patch(args.id, updates);
465
- return args.id;
466
- },
467
- });
468
-
469
- export const remove = mutation({
470
- args: { id: v.id("objects") },
471
- handler: async (ctx, args) => {
472
- const existing = await ctx.db.get(args.id);
473
- if (!existing) {
474
- throw new Error("Object not found");
475
- }
476
-
477
- // Soft delete
478
- await ctx.db.patch(args.id, {
479
- deletedAt: Date.now(),
480
- updatedAt: Date.now(),
481
- syncStatus: existing.l4yercak3Id ? "pending_push" : "local_only",
482
- });
483
-
484
- return args.id;
485
- },
486
- });
487
-
488
- // ============ Links ============
489
-
490
- export const createLink = mutation({
491
- args: {
492
- fromObjectId: v.id("objects"),
493
- toObjectId: v.id("objects"),
494
- linkType: v.string(),
495
- metadata: v.optional(v.any()),
496
- },
497
- handler: async (ctx, args) => {
498
- const id = await ctx.db.insert("objectLinks", {
499
- fromObjectId: args.fromObjectId,
500
- toObjectId: args.toObjectId,
501
- linkType: args.linkType,
502
- metadata: args.metadata,
503
- syncStatus: "local_only",
504
- createdAt: Date.now(),
505
- });
506
-
507
- return id;
508
- },
509
- });
510
-
511
- export const getLinks = query({
512
- args: {
513
- objectId: v.id("objects"),
514
- linkType: v.optional(v.string()),
515
- direction: v.optional(v.union(v.literal("from"), v.literal("to"))),
516
- },
517
- handler: async (ctx, args) => {
518
- const direction = args.direction || "from";
519
-
520
- let links;
521
- if (direction === "from") {
522
- links = await ctx.db
523
- .query("objectLinks")
524
- .withIndex("by_from", (q) => q.eq("fromObjectId", args.objectId))
525
- .collect();
526
- } else {
527
- links = await ctx.db
528
- .query("objectLinks")
529
- .withIndex("by_to", (q) => q.eq("toObjectId", args.objectId))
530
- .collect();
531
- }
532
-
533
- if (args.linkType) {
534
- links = links.filter((l) => l.linkType === args.linkType);
535
- }
536
-
537
- return links;
538
- },
539
- });
540
-
541
- // ============ Sync Helpers ============
542
-
543
- export const getPendingSync = query({
544
- args: { direction: v.union(v.literal("push"), v.literal("pull")) },
545
- handler: async (ctx, args) => {
546
- const status = args.direction === "push" ? "pending_push" : "pending_pull";
547
-
548
- return ctx.db
549
- .query("objects")
550
- .withIndex("by_sync_status", (q) => q.eq("syncStatus", status))
551
- .collect();
552
- },
553
- });
554
-
555
- export const markSynced = mutation({
556
- args: {
557
- id: v.id("objects"),
558
- l4yercak3Id: v.string(),
559
- remoteVersion: v.optional(v.number()),
560
- },
561
- handler: async (ctx, args) => {
562
- await ctx.db.patch(args.id, {
563
- l4yercak3Id: args.l4yercak3Id,
564
- syncStatus: "synced",
565
- syncedAt: Date.now(),
566
- remoteVersion: args.remoteVersion,
567
- });
568
- },
569
- });
570
- `;
571
-
572
- return writeFileWithBackup(outputPath, content, action);
573
- }
574
-
575
- async generateFrontendUsers(convexDir) {
576
- const outputPath = path.join(convexDir, 'frontendUsers.ts');
577
-
578
- const action = await checkFileOverwrite(outputPath);
579
- if (action === 'skip') {
580
- return null;
581
- }
582
-
583
- const content = `/**
584
- * Convex Frontend Users Queries and Mutations
585
- * Auto-generated by @l4yercak3/cli
586
- *
587
- * Handles local user authentication and syncs users as CRM contacts.
588
- */
589
-
590
- import { v } from "convex/values";
591
- import { query, mutation } from "./_generated/server";
592
-
593
- // ============ Queries ============
594
-
595
- export const getByEmail = query({
596
- args: { email: v.string() },
597
- handler: async (ctx, args) => {
598
- return ctx.db
599
- .query("frontendUsers")
600
- .withIndex("by_email", (q) => q.eq("email", args.email))
601
- .first();
602
- },
603
- });
604
-
605
- export const get = query({
606
- args: { id: v.id("frontendUsers") },
607
- handler: async (ctx, args) => {
608
- return ctx.db.get(args.id);
609
- },
610
- });
611
-
612
- export const getBySession = query({
613
- args: { sessionToken: v.string() },
614
- handler: async (ctx, args) => {
615
- const session = await ctx.db
616
- .query("sessions")
617
- .withIndex("by_token", (q) => q.eq("sessionToken", args.sessionToken))
618
- .first();
619
-
620
- if (!session || session.expiresAt < Date.now()) {
621
- return null;
622
- }
623
-
624
- return ctx.db.get(session.userId);
625
- },
626
- });
627
-
628
- // ============ Mutations ============
629
-
630
- export const create = mutation({
631
- args: {
632
- email: v.string(),
633
- name: v.optional(v.string()),
634
- firstName: v.optional(v.string()),
635
- lastName: v.optional(v.string()),
636
- image: v.optional(v.string()),
637
- organizationId: v.string(),
638
- role: v.optional(v.string()),
639
- },
640
- handler: async (ctx, args) => {
641
- // Check if user already exists
642
- const existing = await ctx.db
643
- .query("frontendUsers")
644
- .withIndex("by_email", (q) => q.eq("email", args.email))
645
- .first();
646
-
647
- if (existing) {
648
- throw new Error("User with this email already exists");
649
- }
650
-
651
- const now = Date.now();
652
-
653
- const id = await ctx.db.insert("frontendUsers", {
654
- email: args.email,
655
- emailVerified: false,
656
- name: args.name,
657
- firstName: args.firstName,
658
- lastName: args.lastName,
659
- image: args.image,
660
- organizationId: args.organizationId,
661
- role: args.role || "user",
662
- oauthAccounts: [],
663
- syncStatus: "pending_push", // Will sync to L4YERCAK3 as CRM contact
664
- createdAt: now,
665
- updatedAt: now,
666
- });
667
-
668
- return id;
669
- },
670
- });
671
-
672
- export const update = mutation({
673
- args: {
674
- id: v.id("frontendUsers"),
675
- name: v.optional(v.string()),
676
- firstName: v.optional(v.string()),
677
- lastName: v.optional(v.string()),
678
- image: v.optional(v.string()),
679
- phone: v.optional(v.string()),
680
- preferences: v.optional(v.any()),
681
- },
682
- handler: async (ctx, args) => {
683
- const { id, ...updates } = args;
684
-
685
- const existing = await ctx.db.get(id);
686
- if (!existing) {
687
- throw new Error("User not found");
688
- }
689
-
690
- await ctx.db.patch(id, {
691
- ...updates,
692
- updatedAt: Date.now(),
693
- syncStatus: existing.l4yercak3ContactId ? "pending_push" : "pending_push",
694
- });
695
-
696
- return id;
697
- },
698
- });
699
-
700
- export const linkOAuthAccount = mutation({
701
- args: {
702
- userId: v.id("frontendUsers"),
703
- provider: v.string(),
704
- providerAccountId: v.string(),
705
- accessToken: v.optional(v.string()),
706
- refreshToken: v.optional(v.string()),
707
- expiresAt: v.optional(v.number()),
708
- scope: v.optional(v.string()),
709
- },
710
- handler: async (ctx, args) => {
711
- const user = await ctx.db.get(args.userId);
712
- if (!user) {
713
- throw new Error("User not found");
714
- }
715
-
716
- // Check if this OAuth account is already linked
717
- const existingAccount = user.oauthAccounts.find(
718
- (a) =>
719
- a.provider === args.provider &&
720
- a.providerAccountId === args.providerAccountId
721
- );
722
-
723
- let oauthAccounts;
724
- if (existingAccount) {
725
- // Update existing account
726
- oauthAccounts = user.oauthAccounts.map((a) =>
727
- a.provider === args.provider &&
728
- a.providerAccountId === args.providerAccountId
729
- ? {
730
- provider: args.provider,
731
- providerAccountId: args.providerAccountId,
732
- accessToken: args.accessToken,
733
- refreshToken: args.refreshToken,
734
- expiresAt: args.expiresAt,
735
- scope: args.scope,
736
- }
737
- : a
738
- );
739
- } else {
740
- // Add new account
741
- oauthAccounts = [
742
- ...user.oauthAccounts,
743
- {
744
- provider: args.provider,
745
- providerAccountId: args.providerAccountId,
746
- accessToken: args.accessToken,
747
- refreshToken: args.refreshToken,
748
- expiresAt: args.expiresAt,
749
- scope: args.scope,
750
- },
751
- ];
752
- }
753
-
754
- await ctx.db.patch(args.userId, {
755
- oauthAccounts,
756
- emailVerified: true, // OAuth email is verified
757
- updatedAt: Date.now(),
758
- });
759
-
760
- return args.userId;
761
- },
762
- });
763
-
764
- export const createSession = mutation({
765
- args: {
766
- userId: v.id("frontendUsers"),
767
- sessionToken: v.string(),
768
- expiresAt: v.number(),
769
- userAgent: v.optional(v.string()),
770
- ipAddress: v.optional(v.string()),
771
- },
772
- handler: async (ctx, args) => {
773
- const now = Date.now();
774
-
775
- // Update last login
776
- await ctx.db.patch(args.userId, {
777
- lastLoginAt: now,
778
- });
779
-
780
- const id = await ctx.db.insert("sessions", {
781
- userId: args.userId,
782
- sessionToken: args.sessionToken,
783
- expiresAt: args.expiresAt,
784
- userAgent: args.userAgent,
785
- ipAddress: args.ipAddress,
786
- createdAt: now,
787
- });
788
-
789
- return id;
790
- },
791
- });
792
-
793
- export const deleteSession = mutation({
794
- args: { sessionToken: v.string() },
795
- handler: async (ctx, args) => {
796
- const session = await ctx.db
797
- .query("sessions")
798
- .withIndex("by_token", (q) => q.eq("sessionToken", args.sessionToken))
799
- .first();
800
-
801
- if (session) {
802
- await ctx.db.delete(session._id);
803
- }
804
- },
805
- });
806
-
807
- // ============ Sync Helpers ============
808
-
809
- export const markSyncedAsContact = mutation({
810
- args: {
811
- id: v.id("frontendUsers"),
812
- l4yercak3ContactId: v.string(),
813
- },
814
- handler: async (ctx, args) => {
815
- await ctx.db.patch(args.id, {
816
- l4yercak3ContactId: args.l4yercak3ContactId,
817
- syncStatus: "synced",
818
- syncedAt: Date.now(),
819
- });
820
- },
821
- });
822
-
823
- export const getPendingSync = query({
824
- args: {},
825
- handler: async (ctx) => {
826
- const users = await ctx.db.query("frontendUsers").collect();
827
- return users.filter((u) => u.syncStatus === "pending_push");
828
- },
829
- });
830
- `;
831
-
832
- return writeFileWithBackup(outputPath, content, action);
833
- }
834
-
835
- async generateSync(convexDir) {
836
- const outputPath = path.join(convexDir, 'sync.ts');
837
-
838
- const action = await checkFileOverwrite(outputPath);
839
- if (action === 'skip') {
840
- return null;
841
- }
842
-
843
- const content = `/**
844
- * Convex L4YERCAK3 Sync Logic
845
- * Auto-generated by @l4yercak3/cli
846
- *
847
- * Handles bidirectional sync between local Convex and L4YERCAK3 backend.
848
- */
849
-
850
- import { v } from "convex/values";
851
- import { internalMutation, internalQuery, action } from "./_generated/server";
852
- import { internal } from "./_generated/api";
853
-
854
- // ============ Sync Job Management ============
855
-
856
- export const createSyncJob = internalMutation({
857
- args: {
858
- entityType: v.string(),
859
- direction: v.union(
860
- v.literal("push"),
861
- v.literal("pull"),
862
- v.literal("bidirectional")
863
- ),
864
- },
865
- handler: async (ctx, args) => {
866
- const id = await ctx.db.insert("syncJobs", {
867
- entityType: args.entityType,
868
- direction: args.direction,
869
- status: "pending",
870
- processedCount: 0,
871
- startedAt: Date.now(),
872
- });
873
-
874
- return id;
875
- },
876
- });
877
-
878
- export const updateSyncJob = internalMutation({
879
- args: {
880
- id: v.id("syncJobs"),
881
- status: v.optional(
882
- v.union(
883
- v.literal("pending"),
884
- v.literal("running"),
885
- v.literal("completed"),
886
- v.literal("failed")
887
- )
888
- ),
889
- processedCount: v.optional(v.number()),
890
- totalCount: v.optional(v.number()),
891
- cursor: v.optional(v.string()),
892
- errorMessage: v.optional(v.string()),
893
- errorDetails: v.optional(v.any()),
894
- },
895
- handler: async (ctx, args) => {
896
- const { id, ...updates } = args;
897
-
898
- const finalUpdates: Record<string, unknown> = { ...updates };
899
-
900
- if (updates.status === "completed" || updates.status === "failed") {
901
- finalUpdates.completedAt = Date.now();
902
- }
903
-
904
- await ctx.db.patch(id, finalUpdates);
905
- },
906
- });
907
-
908
- // ============ Conflict Resolution ============
909
-
910
- export const recordConflict = internalMutation({
911
- args: {
912
- objectId: v.id("objects"),
913
- localVersion: v.any(),
914
- remoteVersion: v.any(),
915
- conflictType: v.string(),
916
- },
917
- handler: async (ctx, args) => {
918
- const id = await ctx.db.insert("syncConflicts", {
919
- objectId: args.objectId,
920
- localVersion: args.localVersion,
921
- remoteVersion: args.remoteVersion,
922
- conflictType: args.conflictType,
923
- createdAt: Date.now(),
924
- });
925
-
926
- // Mark object as conflicted
927
- await ctx.db.patch(args.objectId, {
928
- syncStatus: "conflict",
929
- });
930
-
931
- return id;
932
- },
933
- });
934
-
935
- export const resolveConflict = internalMutation({
936
- args: {
937
- conflictId: v.id("syncConflicts"),
938
- resolution: v.union(
939
- v.literal("local_wins"),
940
- v.literal("remote_wins"),
941
- v.literal("merged")
942
- ),
943
- mergedData: v.optional(v.any()),
944
- },
945
- handler: async (ctx, args) => {
946
- const conflict = await ctx.db.get(args.conflictId);
947
- if (!conflict) {
948
- throw new Error("Conflict not found");
949
- }
950
-
951
- // Apply resolution
952
- if (args.resolution === "local_wins") {
953
- await ctx.db.patch(conflict.objectId, {
954
- syncStatus: "pending_push",
955
- });
956
- } else if (args.resolution === "remote_wins") {
957
- await ctx.db.patch(conflict.objectId, {
958
- customProperties: conflict.remoteVersion,
959
- syncStatus: "synced",
960
- syncedAt: Date.now(),
961
- });
962
- } else if (args.resolution === "merged" && args.mergedData) {
963
- await ctx.db.patch(conflict.objectId, {
964
- customProperties: args.mergedData,
965
- syncStatus: "pending_push",
966
- });
967
- }
968
-
969
- // Mark conflict as resolved
970
- await ctx.db.patch(args.conflictId, {
971
- resolvedAt: Date.now(),
972
- resolution: args.resolution,
973
- });
974
- },
975
- });
976
-
977
- // ============ Sync Actions ============
978
-
979
- /**
980
- * Push local changes to L4YERCAK3
981
- * Call this action to sync pending local changes to the backend.
982
- */
983
- export const pushToL4yercak3 = action({
984
- args: {
985
- entityType: v.optional(v.string()),
986
- },
987
- handler: async (ctx, args) => {
988
- // This is a placeholder - actual implementation requires:
989
- // 1. L4YERCAK3 API key from environment
990
- // 2. Fetching pending items from Convex
991
- // 3. Making API calls to L4YERCAK3
992
- // 4. Updating sync status
993
-
994
- const apiKey = process.env.L4YERCAK3_API_KEY;
995
- if (!apiKey) {
996
- throw new Error("L4YERCAK3_API_KEY environment variable required");
997
- }
998
-
999
- // Create sync job
1000
- const jobId = await ctx.runMutation(internal.sync.createSyncJob, {
1001
- entityType: args.entityType || "all",
1002
- direction: "push",
1003
- });
1004
-
1005
- await ctx.runMutation(internal.sync.updateSyncJob, {
1006
- id: jobId,
1007
- status: "running",
1008
- });
1009
-
1010
- try {
1011
- // TODO: Implement actual sync logic
1012
- // 1. Query pending_push objects
1013
- // 2. For each object, call L4YERCAK3 API
1014
- // 3. Update local records with l4yercak3Id
1015
- // 4. Mark as synced
1016
-
1017
- await ctx.runMutation(internal.sync.updateSyncJob, {
1018
- id: jobId,
1019
- status: "completed",
1020
- processedCount: 0,
1021
- });
1022
-
1023
- return { success: true, jobId };
1024
- } catch (error) {
1025
- await ctx.runMutation(internal.sync.updateSyncJob, {
1026
- id: jobId,
1027
- status: "failed",
1028
- errorMessage: error instanceof Error ? error.message : "Unknown error",
1029
- });
1030
-
1031
- throw error;
1032
- }
1033
- },
1034
- });
1035
-
1036
- /**
1037
- * Pull changes from L4YERCAK3
1038
- * Call this action to fetch updates from the backend.
1039
- */
1040
- export const pullFromL4yercak3 = action({
1041
- args: {
1042
- entityType: v.optional(v.string()),
1043
- since: v.optional(v.number()),
1044
- },
1045
- handler: async (ctx, args) => {
1046
- const apiKey = process.env.L4YERCAK3_API_KEY;
1047
- if (!apiKey) {
1048
- throw new Error("L4YERCAK3_API_KEY environment variable required");
1049
- }
1050
-
1051
- // Create sync job
1052
- const jobId = await ctx.runMutation(internal.sync.createSyncJob, {
1053
- entityType: args.entityType || "all",
1054
- direction: "pull",
1055
- });
1056
-
1057
- await ctx.runMutation(internal.sync.updateSyncJob, {
1058
- id: jobId,
1059
- status: "running",
1060
- });
1061
-
1062
- try {
1063
- // TODO: Implement actual sync logic
1064
- // 1. Call L4YERCAK3 API to get updated records
1065
- // 2. For each record, check if local version exists
1066
- // 3. If exists and different, create conflict or update
1067
- // 4. If doesn't exist, create new record
1068
-
1069
- await ctx.runMutation(internal.sync.updateSyncJob, {
1070
- id: jobId,
1071
- status: "completed",
1072
- processedCount: 0,
1073
- });
1074
-
1075
- return { success: true, jobId };
1076
- } catch (error) {
1077
- await ctx.runMutation(internal.sync.updateSyncJob, {
1078
- id: jobId,
1079
- status: "failed",
1080
- errorMessage: error instanceof Error ? error.message : "Unknown error",
1081
- });
1082
-
1083
- throw error;
1084
- }
1085
- },
1086
- });
1087
-
1088
- // ============ Queries ============
1089
-
1090
- export const getUnresolvedConflicts = internalQuery({
1091
- args: {},
1092
- handler: async (ctx) => {
1093
- return ctx.db
1094
- .query("syncConflicts")
1095
- .withIndex("by_unresolved", (q) => q.eq("resolvedAt", undefined))
1096
- .collect();
1097
- },
1098
- });
1099
-
1100
- export const getRecentSyncJobs = internalQuery({
1101
- args: { limit: v.optional(v.number()) },
1102
- handler: async (ctx, args) => {
1103
- const jobs = await ctx.db.query("syncJobs").order("desc").collect();
1104
- return jobs.slice(0, args.limit || 10);
1105
- },
1106
- });
1107
- `;
1108
-
1109
- return writeFileWithBackup(outputPath, content, action);
1110
- }
1111
-
1112
- async generateHttp(convexDir) {
1113
- const outputPath = path.join(convexDir, 'http.ts');
1114
-
1115
- const action = await checkFileOverwrite(outputPath);
1116
- if (action === 'skip') {
1117
- return null;
1118
- }
1119
-
1120
- const content = `/**
1121
- * Convex HTTP Endpoints
1122
- * Auto-generated by @l4yercak3/cli
1123
- *
1124
- * Webhook endpoints for L4YERCAK3 and Stripe.
1125
- */
1126
-
1127
- import { httpRouter } from "convex/server";
1128
- import { httpAction } from "./_generated/server";
1129
-
1130
- const http = httpRouter();
1131
-
1132
- /**
1133
- * L4YERCAK3 Webhook Handler
1134
- * Receives events from L4YERCAK3 backend
1135
- */
1136
- http.route({
1137
- path: "/webhooks/l4yercak3",
1138
- method: "POST",
1139
- handler: httpAction(async (ctx, request) => {
1140
- const signature = request.headers.get("x-l4yercak3-signature");
1141
- const webhookSecret = process.env.L4YERCAK3_WEBHOOK_SECRET;
1142
-
1143
- if (!webhookSecret) {
1144
- return new Response("Webhook secret not configured", { status: 500 });
1145
- }
1146
-
1147
- // Get raw body for signature verification
1148
- const body = await request.text();
1149
-
1150
- // TODO: Verify signature using the same logic as webhooks.ts
1151
- // For now, basic validation
1152
- if (!signature) {
1153
- return new Response("Missing signature", { status: 401 });
1154
- }
1155
-
1156
- try {
1157
- const event = JSON.parse(body);
1158
-
1159
- // Handle different event types
1160
- switch (event.type) {
1161
- case "contact.created":
1162
- case "contact.updated":
1163
- // Sync contact to local objects
1164
- console.log("Contact event:", event.type, event.data);
1165
- break;
1166
-
1167
- case "event.created":
1168
- case "event.updated":
1169
- // Sync event to local objects
1170
- console.log("Event event:", event.type, event.data);
1171
- break;
1172
-
1173
- case "order.created":
1174
- case "order.paid":
1175
- // Sync order to local objects
1176
- console.log("Order event:", event.type, event.data);
1177
- break;
1178
-
1179
- default:
1180
- console.log("Unhandled event type:", event.type);
1181
- }
1182
-
1183
- return new Response("OK", { status: 200 });
1184
- } catch (error) {
1185
- console.error("Webhook error:", error);
1186
- return new Response("Invalid payload", { status: 400 });
1187
- }
1188
- }),
1189
- });
1190
-
1191
- /**
1192
- * Stripe Webhook Handler
1193
- * Receives events from Stripe
1194
- */
1195
- http.route({
1196
- path: "/webhooks/stripe",
1197
- method: "POST",
1198
- handler: httpAction(async (ctx, request) => {
1199
- const signature = request.headers.get("stripe-signature");
1200
- const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
1201
-
1202
- if (!webhookSecret) {
1203
- return new Response("Stripe webhook secret not configured", {
1204
- status: 500,
1205
- });
1206
- }
1207
-
1208
- if (!signature) {
1209
- return new Response("Missing Stripe signature", { status: 401 });
1210
- }
1211
-
1212
- const body = await request.text();
1213
-
1214
- try {
1215
- // TODO: Verify Stripe signature
1216
- // const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
1217
- // const event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
1218
-
1219
- const event = JSON.parse(body);
1220
-
1221
- // Handle Stripe events
1222
- switch (event.type) {
1223
- case "payment_intent.succeeded":
1224
- console.log("Payment succeeded:", event.data.object.id);
1225
- // Store payment locally and sync to L4YERCAK3
1226
- break;
1227
-
1228
- case "payment_intent.payment_failed":
1229
- console.log("Payment failed:", event.data.object.id);
1230
- break;
1231
-
1232
- case "customer.subscription.created":
1233
- case "customer.subscription.updated":
1234
- case "customer.subscription.deleted":
1235
- console.log("Subscription event:", event.type);
1236
- break;
1237
-
1238
- default:
1239
- console.log("Unhandled Stripe event:", event.type);
1240
- }
1241
-
1242
- return new Response("OK", { status: 200 });
1243
- } catch (error) {
1244
- console.error("Stripe webhook error:", error);
1245
- return new Response("Invalid payload", { status: 400 });
1246
- }
1247
- }),
1248
- });
1249
-
1250
- export default http;
1251
- `;
1252
-
1253
- return writeFileWithBackup(outputPath, content, action);
1254
- }
1255
- }
1256
-
1257
- module.exports = new ConvexGenerator();