@statezero/core 0.2.38 → 0.2.39

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 (294) hide show
  1. package/dist/actions/backend1/django_app/calculate-hash.d.ts +57 -0
  2. package/dist/actions/backend1/django_app/calculate-hash.js +80 -0
  3. package/dist/actions/backend1/django_app/calculate-hash.schema.json +148 -0
  4. package/dist/actions/backend1/django_app/get-current-username.d.ts +29 -0
  5. package/dist/actions/backend1/django_app/get-current-username.js +65 -0
  6. package/dist/actions/backend1/django_app/get-current-username.schema.json +47 -0
  7. package/dist/actions/backend1/django_app/get-server-status.d.ts +38 -0
  8. package/dist/actions/backend1/django_app/get-server-status.js +68 -0
  9. package/dist/actions/backend1/django_app/get-server-status.schema.json +93 -0
  10. package/dist/actions/backend1/django_app/get-user-info.d.ts +44 -0
  11. package/dist/actions/backend1/django_app/get-user-info.js +70 -0
  12. package/dist/actions/backend1/django_app/get-user-info.schema.json +127 -0
  13. package/dist/actions/backend1/django_app/index.d.ts +1 -0
  14. package/dist/actions/backend1/django_app/index.js +6 -0
  15. package/dist/actions/backend1/django_app/process-data.d.ts +51 -0
  16. package/dist/actions/backend1/django_app/process-data.js +78 -0
  17. package/dist/actions/backend1/django_app/process-data.schema.json +117 -0
  18. package/dist/actions/backend1/django_app/send-notification.d.ts +55 -0
  19. package/dist/actions/backend1/django_app/send-notification.js +81 -0
  20. package/dist/actions/backend1/django_app/send-notification.schema.json +175 -0
  21. package/dist/actions/backend1/index.d.ts +1 -0
  22. package/dist/actions/backend1/index.js +1 -0
  23. package/dist/actions/default/django_app/calculate-hash.d.ts +57 -0
  24. package/dist/actions/default/django_app/calculate-hash.js +80 -0
  25. package/dist/actions/default/django_app/calculate-hash.schema.json +148 -0
  26. package/dist/actions/default/django_app/get-current-username.d.ts +29 -0
  27. package/dist/actions/default/django_app/get-current-username.js +65 -0
  28. package/dist/actions/default/django_app/get-current-username.schema.json +47 -0
  29. package/dist/actions/default/django_app/get-server-status.d.ts +38 -0
  30. package/dist/actions/default/django_app/get-server-status.js +68 -0
  31. package/dist/actions/default/django_app/get-server-status.schema.json +93 -0
  32. package/dist/actions/default/django_app/get-user-info.d.ts +44 -0
  33. package/dist/actions/default/django_app/get-user-info.js +70 -0
  34. package/dist/actions/default/django_app/get-user-info.schema.json +127 -0
  35. package/dist/actions/default/django_app/index.d.ts +1 -0
  36. package/dist/actions/default/django_app/index.js +6 -0
  37. package/dist/actions/default/django_app/process-data.d.ts +51 -0
  38. package/dist/actions/default/django_app/process-data.js +78 -0
  39. package/dist/actions/default/django_app/process-data.schema.json +117 -0
  40. package/dist/actions/default/django_app/send-notification.d.ts +55 -0
  41. package/dist/actions/default/django_app/send-notification.js +81 -0
  42. package/dist/actions/default/django_app/send-notification.schema.json +175 -0
  43. package/dist/actions/default/index.d.ts +1 -0
  44. package/dist/actions/default/index.js +1 -0
  45. package/dist/actions/index.d.ts +1 -0
  46. package/dist/actions/index.js +5 -0
  47. package/dist/adaptors/react/composables.d.ts +1 -0
  48. package/dist/adaptors/react/composables.js +4 -0
  49. package/dist/adaptors/react/index.d.ts +1 -0
  50. package/dist/adaptors/react/index.js +1 -0
  51. package/dist/adaptors/vue/components/LayoutRenderer.js +46 -49
  52. package/dist/adaptors/vue/components/defaults/index.d.ts +7 -0
  53. package/dist/adaptors/vue/components/defaults/index.js +31 -0
  54. package/dist/adaptors/vue/components/index.d.ts +1 -0
  55. package/dist/adaptors/vue/components/index.js +7 -0
  56. package/dist/adaptors/vue/composables.d.ts +2 -0
  57. package/dist/adaptors/vue/composables.js +44 -0
  58. package/dist/adaptors/vue/index.d.ts +3 -0
  59. package/dist/adaptors/vue/index.js +4 -0
  60. package/dist/adaptors/vue/reactivity.d.ts +18 -0
  61. package/dist/adaptors/vue/reactivity.js +132 -0
  62. package/dist/cli/commands/sync.d.ts +6 -0
  63. package/dist/cli/commands/sync.js +30 -0
  64. package/dist/cli/commands/syncActions.d.ts +46 -0
  65. package/dist/cli/commands/syncActions.js +717 -0
  66. package/dist/cli/commands/syncModels.d.ts +132 -0
  67. package/dist/cli/commands/syncModels.js +1120 -0
  68. package/dist/cli/configFileLoader.d.ts +10 -0
  69. package/dist/cli/configFileLoader.js +85 -0
  70. package/dist/cli/index.d.ts +2 -0
  71. package/dist/cli/index.js +22 -0
  72. package/dist/config.d.ts +57 -0
  73. package/dist/config.js +273 -0
  74. package/dist/core/eventReceivers.d.ts +185 -0
  75. package/dist/core/eventReceivers.js +266 -0
  76. package/dist/core/utils.d.ts +8 -0
  77. package/dist/core/utils.js +62 -0
  78. package/dist/errorHandler.d.ts +21 -0
  79. package/dist/errorHandler.js +27 -0
  80. package/dist/filtering/localFiltering.d.ts +110 -0
  81. package/dist/filtering/localFiltering.js +1080 -0
  82. package/dist/flavours/django/dates.d.ts +34 -0
  83. package/dist/flavours/django/dates.js +113 -0
  84. package/dist/flavours/django/errors.d.ts +138 -0
  85. package/dist/flavours/django/errors.js +195 -0
  86. package/dist/flavours/django/f.d.ts +6 -0
  87. package/dist/flavours/django/f.js +91 -0
  88. package/dist/flavours/django/files.d.ts +62 -0
  89. package/dist/flavours/django/files.js +355 -0
  90. package/dist/flavours/django/makeApiCall.d.ts +36 -0
  91. package/dist/flavours/django/makeApiCall.js +169 -0
  92. package/dist/flavours/django/manager.d.ts +204 -0
  93. package/dist/flavours/django/manager.js +222 -0
  94. package/dist/flavours/django/model.d.ts +137 -0
  95. package/dist/flavours/django/model.js +366 -0
  96. package/dist/flavours/django/operationFactory.d.ts +73 -0
  97. package/dist/flavours/django/operationFactory.js +248 -0
  98. package/dist/flavours/django/q.d.ts +70 -0
  99. package/dist/flavours/django/q.js +43 -0
  100. package/dist/flavours/django/queryExecutor.d.ts +149 -0
  101. package/dist/flavours/django/queryExecutor.js +590 -0
  102. package/dist/flavours/django/querySet.d.ts +301 -0
  103. package/dist/flavours/django/querySet.js +736 -0
  104. package/dist/flavours/django/serializers.d.ts +39 -0
  105. package/dist/flavours/django/serializers.js +296 -0
  106. package/dist/flavours/django/tempPk.d.ts +31 -0
  107. package/dist/flavours/django/tempPk.js +92 -0
  108. package/dist/flavours/django/utils.d.ts +19 -0
  109. package/dist/flavours/django/utils.js +29 -0
  110. package/dist/index.d.ts +46 -0
  111. package/dist/index.js +48 -0
  112. package/dist/models/backend1/django_app/comprehensivemodel.d.ts +894 -0
  113. package/dist/models/backend1/django_app/comprehensivemodel.js +71 -0
  114. package/dist/models/backend1/django_app/comprehensivemodel.schema.json +870 -0
  115. package/dist/models/backend1/django_app/custompkmodel.d.ts +92 -0
  116. package/dist/models/backend1/django_app/custompkmodel.js +69 -0
  117. package/dist/models/backend1/django_app/custompkmodel.schema.json +71 -0
  118. package/dist/models/backend1/django_app/dailyrate.d.ts +230 -0
  119. package/dist/models/backend1/django_app/dailyrate.js +71 -0
  120. package/dist/models/backend1/django_app/dailyrate.schema.json +212 -0
  121. package/dist/models/backend1/django_app/deepmodellevel1.d.ts +140 -0
  122. package/dist/models/backend1/django_app/deepmodellevel1.js +72 -0
  123. package/dist/models/backend1/django_app/deepmodellevel1.schema.json +114 -0
  124. package/dist/models/backend1/django_app/deepmodellevel2.d.ts +118 -0
  125. package/dist/models/backend1/django_app/deepmodellevel2.js +71 -0
  126. package/dist/models/backend1/django_app/deepmodellevel2.schema.json +92 -0
  127. package/dist/models/backend1/django_app/deepmodellevel3.d.ts +92 -0
  128. package/dist/models/backend1/django_app/deepmodellevel3.js +69 -0
  129. package/dist/models/backend1/django_app/deepmodellevel3.schema.json +69 -0
  130. package/dist/models/backend1/django_app/dummymodel.d.ts +134 -0
  131. package/dist/models/backend1/django_app/dummymodel.js +71 -0
  132. package/dist/models/backend1/django_app/dummymodel.schema.json +109 -0
  133. package/dist/models/backend1/django_app/dummyrelatedmodel.d.ts +92 -0
  134. package/dist/models/backend1/django_app/dummyrelatedmodel.js +69 -0
  135. package/dist/models/backend1/django_app/dummyrelatedmodel.schema.json +69 -0
  136. package/dist/models/backend1/django_app/filetest.d.ts +140 -0
  137. package/dist/models/backend1/django_app/filetest.js +69 -0
  138. package/dist/models/backend1/django_app/filetest.schema.json +111 -0
  139. package/dist/models/backend1/django_app/index.d.ts +1 -0
  140. package/dist/models/backend1/django_app/index.js +21 -0
  141. package/dist/models/backend1/django_app/m2mdepthtestlevel1.d.ts +118 -0
  142. package/dist/models/backend1/django_app/m2mdepthtestlevel1.js +71 -0
  143. package/dist/models/backend1/django_app/m2mdepthtestlevel1.schema.json +94 -0
  144. package/dist/models/backend1/django_app/m2mdepthtestlevel2.d.ts +118 -0
  145. package/dist/models/backend1/django_app/m2mdepthtestlevel2.js +71 -0
  146. package/dist/models/backend1/django_app/m2mdepthtestlevel2.schema.json +94 -0
  147. package/dist/models/backend1/django_app/m2mdepthtestlevel3.d.ts +134 -0
  148. package/dist/models/backend1/django_app/m2mdepthtestlevel3.js +71 -0
  149. package/dist/models/backend1/django_app/m2mdepthtestlevel3.schema.json +112 -0
  150. package/dist/models/backend1/django_app/modelwithcustompkrelation.d.ts +118 -0
  151. package/dist/models/backend1/django_app/modelwithcustompkrelation.js +71 -0
  152. package/dist/models/backend1/django_app/modelwithcustompkrelation.schema.json +93 -0
  153. package/dist/models/backend1/django_app/modelwithrestrictedfields.d.ts +134 -0
  154. package/dist/models/backend1/django_app/modelwithrestrictedfields.js +71 -0
  155. package/dist/models/backend1/django_app/modelwithrestrictedfields.schema.json +111 -0
  156. package/dist/models/backend1/django_app/namefiltercustompkmodel.d.ts +92 -0
  157. package/dist/models/backend1/django_app/namefiltercustompkmodel.js +69 -0
  158. package/dist/models/backend1/django_app/namefiltercustompkmodel.schema.json +71 -0
  159. package/dist/models/backend1/django_app/order.d.ts +220 -0
  160. package/dist/models/backend1/django_app/order.js +71 -0
  161. package/dist/models/backend1/django_app/order.schema.json +203 -0
  162. package/dist/models/backend1/django_app/orderitem.d.ts +172 -0
  163. package/dist/models/backend1/django_app/orderitem.js +72 -0
  164. package/dist/models/backend1/django_app/orderitem.schema.json +149 -0
  165. package/dist/models/backend1/django_app/product.d.ts +254 -0
  166. package/dist/models/backend1/django_app/product.js +71 -0
  167. package/dist/models/backend1/django_app/product.schema.json +277 -0
  168. package/dist/models/backend1/django_app/productcategory.d.ts +92 -0
  169. package/dist/models/backend1/django_app/productcategory.js +69 -0
  170. package/dist/models/backend1/django_app/productcategory.schema.json +70 -0
  171. package/dist/models/backend1/django_app/rateplan.d.ts +92 -0
  172. package/dist/models/backend1/django_app/rateplan.js +69 -0
  173. package/dist/models/backend1/django_app/rateplan.schema.json +70 -0
  174. package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.d.ts +108 -0
  175. package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.js +69 -0
  176. package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.schema.json +87 -0
  177. package/dist/models/backend1/fileobject.d.ts +4 -0
  178. package/dist/models/backend1/fileobject.js +9 -0
  179. package/dist/models/backend1/index.d.ts +2 -0
  180. package/dist/models/backend1/index.js +2 -0
  181. package/dist/models/default/django_app/comprehensivemodel.d.ts +894 -0
  182. package/dist/models/default/django_app/comprehensivemodel.js +71 -0
  183. package/dist/models/default/django_app/comprehensivemodel.schema.json +870 -0
  184. package/dist/models/default/django_app/custompkmodel.d.ts +92 -0
  185. package/dist/models/default/django_app/custompkmodel.js +69 -0
  186. package/dist/models/default/django_app/custompkmodel.schema.json +71 -0
  187. package/dist/models/default/django_app/dailyrate.d.ts +230 -0
  188. package/dist/models/default/django_app/dailyrate.js +71 -0
  189. package/dist/models/default/django_app/dailyrate.schema.json +212 -0
  190. package/dist/models/default/django_app/deepmodellevel1.d.ts +128 -0
  191. package/dist/models/default/django_app/deepmodellevel1.js +72 -0
  192. package/dist/models/default/django_app/deepmodellevel1.schema.json +102 -0
  193. package/dist/models/default/django_app/deepmodellevel2.d.ts +106 -0
  194. package/dist/models/default/django_app/deepmodellevel2.js +71 -0
  195. package/dist/models/default/django_app/deepmodellevel2.schema.json +80 -0
  196. package/dist/models/default/django_app/deepmodellevel3.d.ts +80 -0
  197. package/dist/models/default/django_app/deepmodellevel3.js +69 -0
  198. package/dist/models/default/django_app/deepmodellevel3.schema.json +57 -0
  199. package/dist/models/default/django_app/dummymodel.d.ts +122 -0
  200. package/dist/models/default/django_app/dummymodel.js +71 -0
  201. package/dist/models/default/django_app/dummymodel.schema.json +97 -0
  202. package/dist/models/default/django_app/dummyrelatedmodel.d.ts +80 -0
  203. package/dist/models/default/django_app/dummyrelatedmodel.js +69 -0
  204. package/dist/models/default/django_app/dummyrelatedmodel.schema.json +57 -0
  205. package/dist/models/default/django_app/filetest.d.ts +128 -0
  206. package/dist/models/default/django_app/filetest.js +69 -0
  207. package/dist/models/default/django_app/filetest.schema.json +99 -0
  208. package/dist/models/default/django_app/index.d.ts +1 -0
  209. package/dist/models/default/django_app/index.js +21 -0
  210. package/dist/models/default/django_app/m2mdepthtestlevel1.d.ts +118 -0
  211. package/dist/models/default/django_app/m2mdepthtestlevel1.js +71 -0
  212. package/dist/models/default/django_app/m2mdepthtestlevel1.schema.json +94 -0
  213. package/dist/models/default/django_app/m2mdepthtestlevel2.d.ts +118 -0
  214. package/dist/models/default/django_app/m2mdepthtestlevel2.js +71 -0
  215. package/dist/models/default/django_app/m2mdepthtestlevel2.schema.json +94 -0
  216. package/dist/models/default/django_app/m2mdepthtestlevel3.d.ts +134 -0
  217. package/dist/models/default/django_app/m2mdepthtestlevel3.js +71 -0
  218. package/dist/models/default/django_app/m2mdepthtestlevel3.schema.json +112 -0
  219. package/dist/models/default/django_app/modelwithcustompkrelation.d.ts +118 -0
  220. package/dist/models/default/django_app/modelwithcustompkrelation.js +71 -0
  221. package/dist/models/default/django_app/modelwithcustompkrelation.schema.json +93 -0
  222. package/dist/models/default/django_app/modelwithrestrictedfields.d.ts +134 -0
  223. package/dist/models/default/django_app/modelwithrestrictedfields.js +71 -0
  224. package/dist/models/default/django_app/modelwithrestrictedfields.schema.json +111 -0
  225. package/dist/models/default/django_app/namefiltercustompkmodel.d.ts +92 -0
  226. package/dist/models/default/django_app/namefiltercustompkmodel.js +69 -0
  227. package/dist/models/default/django_app/namefiltercustompkmodel.schema.json +71 -0
  228. package/dist/models/default/django_app/order.d.ts +220 -0
  229. package/dist/models/default/django_app/order.js +71 -0
  230. package/dist/models/default/django_app/order.schema.json +203 -0
  231. package/dist/models/default/django_app/orderitem.d.ts +172 -0
  232. package/dist/models/default/django_app/orderitem.js +72 -0
  233. package/dist/models/default/django_app/orderitem.schema.json +149 -0
  234. package/dist/models/default/django_app/product.d.ts +254 -0
  235. package/dist/models/default/django_app/product.js +71 -0
  236. package/dist/models/default/django_app/product.schema.json +277 -0
  237. package/dist/models/default/django_app/productcategory.d.ts +92 -0
  238. package/dist/models/default/django_app/productcategory.js +69 -0
  239. package/dist/models/default/django_app/productcategory.schema.json +70 -0
  240. package/dist/models/default/django_app/rateplan.d.ts +92 -0
  241. package/dist/models/default/django_app/rateplan.js +69 -0
  242. package/dist/models/default/django_app/rateplan.schema.json +70 -0
  243. package/dist/models/default/django_app/restrictedfieldrelatedmodel.d.ts +108 -0
  244. package/dist/models/default/django_app/restrictedfieldrelatedmodel.js +69 -0
  245. package/dist/models/default/django_app/restrictedfieldrelatedmodel.schema.json +87 -0
  246. package/dist/models/default/fileobject.d.ts +4 -0
  247. package/dist/models/default/fileobject.js +9 -0
  248. package/dist/models/default/index.d.ts +2 -0
  249. package/dist/models/default/index.js +2 -0
  250. package/dist/models/index.d.ts +1 -0
  251. package/dist/models/index.js +5 -0
  252. package/dist/react-entry.d.ts +2 -0
  253. package/dist/react-entry.js +2 -0
  254. package/dist/reactiveAdaptor.d.ts +24 -0
  255. package/dist/reactiveAdaptor.js +38 -0
  256. package/dist/reset.d.ts +15 -0
  257. package/dist/reset.js +97 -0
  258. package/dist/setup.d.ts +15 -0
  259. package/dist/setup.js +33 -0
  260. package/dist/syncEngine/cache/cache.d.ts +75 -0
  261. package/dist/syncEngine/cache/cache.js +355 -0
  262. package/dist/syncEngine/metrics/metricOptCalcs.d.ts +79 -0
  263. package/dist/syncEngine/metrics/metricOptCalcs.js +284 -0
  264. package/dist/syncEngine/registries/metricRegistry.d.ts +58 -0
  265. package/dist/syncEngine/registries/metricRegistry.js +171 -0
  266. package/dist/syncEngine/registries/modelStoreRegistry.d.ts +11 -0
  267. package/dist/syncEngine/registries/modelStoreRegistry.js +63 -0
  268. package/dist/syncEngine/registries/querysetStoreGraph.d.ts +41 -0
  269. package/dist/syncEngine/registries/querysetStoreGraph.js +174 -0
  270. package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +72 -0
  271. package/dist/syncEngine/registries/querysetStoreRegistry.js +335 -0
  272. package/dist/syncEngine/stores/metricStore.d.ts +55 -0
  273. package/dist/syncEngine/stores/metricStore.js +222 -0
  274. package/dist/syncEngine/stores/modelStore.d.ts +53 -0
  275. package/dist/syncEngine/stores/modelStore.js +565 -0
  276. package/dist/syncEngine/stores/operation.d.ts +139 -0
  277. package/dist/syncEngine/stores/operation.js +291 -0
  278. package/dist/syncEngine/stores/operationEventHandlers.d.ts +8 -0
  279. package/dist/syncEngine/stores/operationEventHandlers.js +322 -0
  280. package/dist/syncEngine/stores/querysetStore.d.ts +60 -0
  281. package/dist/syncEngine/stores/querysetStore.js +294 -0
  282. package/dist/syncEngine/stores/reactivity.d.ts +3 -0
  283. package/dist/syncEngine/stores/reactivity.js +4 -0
  284. package/dist/syncEngine/stores/utils.d.ts +14 -0
  285. package/dist/syncEngine/stores/utils.js +32 -0
  286. package/dist/syncEngine/sync.d.ts +46 -0
  287. package/dist/syncEngine/sync.js +389 -0
  288. package/dist/testing.d.ts +63 -0
  289. package/dist/testing.js +175 -0
  290. package/dist/vue-entry.d.ts +15 -0
  291. package/dist/vue-entry.js +7 -0
  292. package/package.json +6 -7
  293. package/dist/adaptors/vue/components/layout.tailwind.css +0 -51
  294. /package/{dist → src}/adaptors/vue/components/layout.css +0 -0
@@ -0,0 +1,1120 @@
1
+ import axios from "axios";
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ import cliProgress from "cli-progress";
5
+ import Handlebars from "handlebars";
6
+ import _ from "lodash-es";
7
+ import { configInstance } from "../../config.js"; // Global config singleton
8
+ import { loadConfigFromFile } from "../configFileLoader.js";
9
+ // --------------------
10
+ // JSDoc Type Definitions
11
+ // --------------------
12
+ /**
13
+ * @typedef {Object} GenerateArgs
14
+ * // Additional arguments for generation if needed.
15
+ */
16
+ /**
17
+ * @typedef {Object} SchemaProperty
18
+ * @property {string} type
19
+ * @property {string} [format]
20
+ * @property {SchemaProperty} [items]
21
+ * @property {Object.<string, SchemaProperty>} [properties]
22
+ * @property {string[]} [required]
23
+ * @property {string[]} [enum]
24
+ * @property {string} [description]
25
+ * @property {boolean} [nullable]
26
+ * @property {any} [default]
27
+ * @property {string} [ref]
28
+ */
29
+ /**
30
+ * @typedef {Object} RelationshipField
31
+ * @property {string} field - Name of the relationship field
32
+ * @property {string} ModelClass - The class name of the related model
33
+ * @property {string} relationshipType - Type of relationship (e.g., "many-to-many", "foreign-key")
34
+ */
35
+ /**
36
+ * @typedef {Object} RelationshipData
37
+ * @property {string} type
38
+ * @property {string} model - e.g. "django_app.deepmodellevel1"
39
+ * @property {string} class_name - e.g. "DeepModelLevel1"
40
+ * @property {string} primary_key_field
41
+ */
42
+ /**
43
+ * @typedef {Object} SchemaDefinition
44
+ * @property {string} type
45
+ * @property {Object.<string, SchemaProperty>} properties
46
+ * @property {string[]} [required]
47
+ * @property {string} [description]
48
+ * @property {Object.<string, SchemaDefinition>} [definitions]
49
+ * @property {string} model_name
50
+ * @property {string} class_name
51
+ * @property {string} [primary_key_field]
52
+ * @property {Object.<string, RelationshipData>} [relationships]
53
+ */
54
+ /**
55
+ * @typedef {Object} PropertyDefinition
56
+ * @property {string} name
57
+ * @property {string} type
58
+ * @property {boolean} required
59
+ * @property {string} defaultValue
60
+ * @property {boolean} [isRelationship]
61
+ * @property {string} [relationshipClassName]
62
+ * @property {boolean} [isArrayRelationship]
63
+ * @property {string} [relationshipPrimaryKeyField]
64
+ * @property {boolean} [isString]
65
+ * @property {boolean} [isNumber]
66
+ * @property {boolean} [isBoolean]
67
+ * @property {boolean} [isDate]
68
+ * @property {boolean} [isPrimaryKey]
69
+ */
70
+ /**
71
+ * @typedef {Object} TemplateData
72
+ * @property {string} modulePath - Dynamic module path for imports.
73
+ * @property {string} className - Exported full model class name (from schema.class_name).
74
+ * @property {string} interfaceName - Full model fields interface name (e.g. DeepModelLevel1Fields).
75
+ * @property {string} modelName - Raw schema.model_name (including app label path).
76
+ * @property {PropertyDefinition[]} properties
77
+ * @property {RelationshipField[]} relationshipFields - List of relationship fields for the model
78
+ * @property {string} [description]
79
+ * @property {string[]} [definitions]
80
+ * @property {string[]} [jsImports] - For JS generation: full class imports.
81
+ * @property {string[]} [tsImports] - For TS generation: type imports (Fields).
82
+ * @property {string} configKey - The backend config key.
83
+ * @property {string} primaryKeyField - Primary key field from schema.
84
+ */
85
+ /**
86
+ * @typedef {Object} BackendConfig
87
+ * @property {string} NAME
88
+ * @property {string} API_URL
89
+ * @property {string} GENERATED_TYPES_DIR
90
+ */
91
+ /**
92
+ * @typedef {Object} SelectedModel
93
+ * @property {BackendConfig} backend
94
+ * @property {string} model
95
+ */
96
+ // --------------------
97
+ // Fallback Selection for Inquirer Errors
98
+ // --------------------
99
+ /**
100
+ * Simple fallback that generates all models when inquirer fails
101
+ * @param {Array} choices - Array of choice objects with {name, value, checked} properties
102
+ * @param {string} message - Selection message
103
+ * @returns {Promise<Array>} - All model values
104
+ */
105
+ async function fallbackSelectAll(choices, message) {
106
+ console.log(`\n${message}`);
107
+ console.log("Interactive selection not available - generating ALL models:");
108
+ const allModels = [];
109
+ for (const choice of choices) {
110
+ // Skip separators (they don't have a 'value' property)
111
+ if (!choice.value) {
112
+ console.log(choice.name); // Print separator text
113
+ continue;
114
+ }
115
+ // Add ALL models, regardless of checked status
116
+ allModels.push(choice.value);
117
+ console.log(` ✓ ${choice.name}`);
118
+ }
119
+ console.log(`\nGenerating ALL ${allModels.length} models.`);
120
+ return allModels;
121
+ }
122
+ /**
123
+ * Model selection with inquirer fallback
124
+ * @param {Array} choices - Array of choice objects
125
+ * @param {string} message - Selection message
126
+ * @returns {Promise<Array>} - Selected model objects
127
+ */
128
+ async function selectModels(choices, message) {
129
+ try {
130
+ // Try to use inquirer first
131
+ const inquirer = (await import("inquirer")).default;
132
+ const { selectedModels } = await inquirer.prompt([
133
+ {
134
+ type: "checkbox",
135
+ name: "selectedModels",
136
+ message,
137
+ choices,
138
+ pageSize: 20,
139
+ },
140
+ ]);
141
+ return selectedModels;
142
+ }
143
+ catch (error) {
144
+ // Fall back to generating all models if inquirer fails for any reason
145
+ console.warn("Interactive selection failed, generating all models:", error.message);
146
+ return await fallbackSelectAll(choices, message);
147
+ }
148
+ }
149
+ // --------------------
150
+ // Handlebars Templates & Helpers
151
+ // --------------------
152
+ // Updated JS_MODEL_TEMPLATE with getters and setters
153
+ const JS_MODEL_TEMPLATE = `/**
154
+ * This file was auto-generated. Do not make direct changes to the file.
155
+ {{#if description}}
156
+ * {{description}}
157
+ {{/if}}
158
+ */
159
+
160
+ import { Model, Manager, QuerySet, getModelClass } from '{{modulePath}}';
161
+ import { wrapReactiveModel } from '{{modulePath}}';
162
+ import schemaData from './{{toLowerCase className}}.schema.json';
163
+
164
+ /**
165
+ * Model-specific QuerySet implementation
166
+ */
167
+ export class {{className}}QuerySet extends QuerySet {
168
+ // QuerySet implementation with model-specific typing
169
+ }
170
+
171
+ /**
172
+ * Model-specific Manager implementation
173
+ */
174
+ export class {{className}}Manager extends Manager {
175
+ constructor(ModelClass) {
176
+ super(ModelClass, {{className}}QuerySet);
177
+ }
178
+
179
+ newQuerySet() {
180
+ return new {{className}}QuerySet(this.ModelClass);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Implementation of the {{className}} model
186
+ */
187
+ export class {{className}} extends Model {
188
+ // Bind this model to its backend
189
+ static configKey = '{{configKey}}';
190
+ static modelName = '{{modelName}}';
191
+ static primaryKeyField = '{{primaryKeyField}}';
192
+ static objects = new {{className}}Manager({{className}});
193
+ static fields = [{{#each properties}}'{{name}}'{{#unless @last}}, {{/unless}}{{/each}}];
194
+ static schema = schemaData;
195
+ static relationshipFields = new Map([
196
+ {{#each relationshipFields}}
197
+ ['{{field}}', { 'ModelClass': () => getModelClass('{{modelName}}', '{{../configKey}}'), 'relationshipType': '{{relationshipType}}' }]{{#unless @last}},{{/unless}}
198
+ {{/each}}
199
+ ]);
200
+
201
+ constructor(data) {
202
+ {{className}}.validateFields(data);
203
+ super(data);
204
+
205
+ // Define getters and setters for all fields
206
+ this._defineProperties();
207
+
208
+ return wrapReactiveModel(this);
209
+ }
210
+
211
+ /**
212
+ * Define property getters and setters for all model fields
213
+ * @private
214
+ */
215
+ _defineProperties() {
216
+ // For each field, define a property that gets/sets from internal storage
217
+ {{className}}.fields.forEach(field => {
218
+ Object.defineProperty(this, field, {
219
+ get: function() {
220
+ return this.getField(field);
221
+ },
222
+ set: function(value) {
223
+ this.setField(field, value);
224
+ },
225
+ enumerable: true, // Make sure fields are enumerable for serialization
226
+ configurable: true
227
+ });
228
+ });
229
+
230
+ // Add a special read-only getter for the repr field
231
+ Object.defineProperty(this, 'repr', {
232
+ get: function() {
233
+ return this.getField('repr');
234
+ },
235
+ enumerable: true, // Make sure repr is enumerable
236
+ configurable: true
237
+ });
238
+ }
239
+ }
240
+ `;
241
+ // Updated TS_DECLARATION_TEMPLATE with improved relationship handling
242
+ const TS_DECLARATION_TEMPLATE = `/**
243
+ * This file was auto-generated. Do not make direct changes to the file.
244
+ {{#if description}}
245
+ * {{description}}
246
+ {{/if}}
247
+ */
248
+
249
+ import { Model, Manager } from '{{modulePath}}';
250
+ import { StringOperators, NumberOperators, BooleanOperators, DateOperators } from '{{modulePath}}';
251
+ import { QuerySet, LiveQuerySet, LiveQuerySetOptions, MetricResult, ResultTuple, SerializerOptions, NestedPaths } from '{{modulePath}}';
252
+
253
+ // Re-export the real Manager for runtime use
254
+ import { Manager as RuntimeManager } from '{{modulePath}}';
255
+ {{#if tsImports}}
256
+ {{#each tsImports}}
257
+ {{{this}}}
258
+ {{/each}}
259
+ {{/if}}
260
+
261
+ /**
262
+ * Base fields interface - defines the shape of a model instance
263
+ * This is the single source of truth for the model's data structure
264
+ */
265
+ export interface {{interfaceName}} {
266
+ {{#each properties}}
267
+ {{name}}{{#unless required}}?{{/unless}}: {{{type}}};
268
+ {{/each}}
269
+ // Read-only representation field
270
+ readonly repr: {
271
+ str: string;
272
+ img: string | null;
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Relationship field structure
278
+ */
279
+ export interface RelationshipField {
280
+ ModelClass: any;
281
+ relationshipType: string;
282
+ }
283
+
284
+ /**
285
+ * Relationship fields map type
286
+ */
287
+ export type RelationshipFieldsMap = Map<string, RelationshipField>;
288
+
289
+ /**
290
+ * Type for creating new instances
291
+ * Similar to base fields but makes ID fields optional
292
+ */
293
+ export type {{className}}CreateData = {
294
+ {{#each properties}}
295
+ {{name}}{{#unless isPrimaryKey}}{{#unless required}}?{{/unless}}{{else}}?{{/unless}}: {{{type}}};
296
+ {{/each}}
297
+ };
298
+
299
+ /**
300
+ * Type for updating instances
301
+ * All fields are optional since updates can be partial
302
+ */
303
+ export type {{className}}UpdateData = Partial<{{interfaceName}}>;
304
+
305
+ /**
306
+ * Type for filtering with field lookups
307
+ * Supports advanced filtering with operators like __gte, __contains, etc.
308
+ */
309
+ export interface {{className}}FilterData {
310
+ {{#each properties}}
311
+ {{#if isRelationship}}
312
+ {{#if isArrayRelationship}}
313
+ // Many-to-many relationship field
314
+ {{name}}?: number; // Exact match by ID
315
+ {{name}}__in?: number[]; // Match any of these IDs
316
+ {{name}}__isnull?: boolean; // Check if relation exists
317
+ {{else}}
318
+ // Foreign key relationship field
319
+ {{name}}?: number; // Exact match by ID
320
+ {{name}}__isnull?: boolean; // Check if relation exists
321
+ {{/if}}
322
+ {{else}}
323
+ {{#if isString}}
324
+ {{name}}?: string | StringOperators;
325
+ {{name}}__contains?: string;
326
+ {{name}}__icontains?: string;
327
+ {{name}}__startswith?: string;
328
+ {{name}}__istartswith?: string;
329
+ {{name}}__endswith?: string;
330
+ {{name}}__iendswith?: string;
331
+ {{name}}__exact?: string;
332
+ {{name}}__iexact?: string;
333
+ {{name}}__in?: string[];
334
+ {{name}}__isnull?: boolean;
335
+ {{/if}}
336
+ {{#if isNumber}}
337
+ {{name}}?: number | NumberOperators;
338
+ {{name}}__gt?: number;
339
+ {{name}}__gte?: number;
340
+ {{name}}__lt?: number;
341
+ {{name}}__lte?: number;
342
+ {{name}}__exact?: number;
343
+ {{name}}__in?: number[];
344
+ {{name}}__isnull?: boolean;
345
+ {{/if}}
346
+ {{#if isBoolean}}
347
+ {{name}}?: boolean | BooleanOperators;
348
+ {{name}}__exact?: boolean;
349
+ {{name}}__isnull?: boolean;
350
+ {{/if}}
351
+ {{#if isDate}}
352
+ {{name}}?: Date | DateOperators;
353
+ {{name}}__gt?: Date;
354
+ {{name}}__gte?: Date;
355
+ {{name}}__lt?: Date;
356
+ {{name}}__lte?: Date;
357
+ {{name}}__exact?: Date;
358
+ {{name}}__in?: Date[];
359
+ {{name}}__isnull?: boolean;
360
+ {{/if}}
361
+ {{/if}}
362
+ {{/each}}
363
+ // Support for nested filtering on related fields
364
+ [key: string]: any;
365
+
366
+ // Support for Q objects
367
+ Q?: Array<any>;
368
+ }
369
+
370
+ /**
371
+ * Model-specific QuerySet with strictly typed methods
372
+ */
373
+ export declare class {{className}}QuerySet extends QuerySet<any> {
374
+ // Chain methods
375
+ filter(conditions: {{className}}FilterData): {{className}}QuerySet;
376
+ exclude(conditions: {{className}}FilterData): {{className}}QuerySet;
377
+ orderBy(...fields: Array<keyof {{interfaceName}} | string>): {{className}}QuerySet;
378
+ search(searchQuery: string, searchFields?: Array<string>): {{className}}QuerySet;
379
+
380
+ // Terminal methods
381
+ get(filters?: {{className}}FilterData, serializerOptions?: SerializerOptions): Promise<{{className}}>;
382
+ first(serializerOptions?: SerializerOptions): Promise<{{className}} | null>;
383
+ last(serializerOptions?: SerializerOptions): Promise<{{className}} | null>;
384
+ all(): {{className}}QuerySet;
385
+ count(field?: string): Promise<number>;
386
+ update(updates: {{className}}UpdateData): Promise<[number, Record<string, number>]>;
387
+ delete(): Promise<[number, Record<string, number>]>;
388
+ exists(): Promise<boolean>;
389
+ fetch(serializerOptions?: SerializerOptions): Promise<{{className}}[]>;
390
+ }
391
+
392
+ /**
393
+ * Model-specific Manager with strictly typed methods
394
+ */
395
+ export declare class {{className}}Manager extends Manager {
396
+ newQuerySet(): {{className}}QuerySet;
397
+ filter(conditions: {{className}}FilterData): {{className}}QuerySet;
398
+ exclude(conditions: {{className}}FilterData): {{className}}QuerySet;
399
+ all(): {{className}}QuerySet;
400
+ get(filters?: {{className}}FilterData, serializerOptions?: SerializerOptions): Promise<{{className}}>;
401
+ create(data: {{className}}CreateData): Promise<{{className}}>;
402
+ delete(): Promise<[number, Record<string, number>]>;
403
+ }
404
+
405
+ /**
406
+ * Model-specific LiveQuerySet with strictly typed methods
407
+ */
408
+ export declare class {{className}}LiveQuerySet extends LiveQuerySet {
409
+ // Data access
410
+ get data(): {{className}}[];
411
+
412
+ // Chain methods
413
+ filter(conditions: {{className}}FilterData): {{className}}LiveQuerySet;
414
+
415
+ // Terminal methods
416
+ fetch(serializerOptions?: SerializerOptions): Promise<{{className}}[]>;
417
+ get(filters?: {{className}}FilterData, serializerOptions?: SerializerOptions): Promise<{{className}}>;
418
+ create(item: {{className}}CreateData): Promise<{{className}}>;
419
+ update(updates: {{className}}UpdateData): Promise<{{className}}[]>;
420
+ delete(): Promise<void>;
421
+ count(field?: string): Promise<MetricResult<number>>;
422
+ sum(field: string): Promise<MetricResult<number>>;
423
+ avg(field: string): Promise<MetricResult<number>>;
424
+ min(field: string): Promise<MetricResult<any>>;
425
+ max(field: string): Promise<MetricResult<any>>;
426
+ }
427
+
428
+ /**
429
+ * Enhanced RuntimeManager to provide TypeScript typings
430
+ * This creates a concrete class that both extends RuntimeManager and matches type expectations
431
+ */
432
+ export class {{className}}Manager extends RuntimeManager {
433
+ filter(conditions: {{className}}FilterData): ReturnType<RuntimeManager['filter']> {
434
+ return super.filter(conditions as any);
435
+ }
436
+
437
+ get(filters?: {{className}}FilterData, serializerOptions?: SerializerOptions): Promise<{{className}}> {
438
+ return super.get(filters as any, serializerOptions);
439
+ }
440
+
441
+ all() {
442
+ return super.all();
443
+ }
444
+
445
+ create(data: {{className}}CreateData): Promise<{{className}}> {
446
+ return super.create(data);
447
+ }
448
+
449
+ update(data: {{className}}UpdateData): Promise<any> {
450
+ return super.update(data);
451
+ }
452
+ }
453
+
454
+ // Class declarations
455
+ export declare class {{className}} extends Model implements {{interfaceName}} {
456
+ {{#each properties}}
457
+ {{name}}{{#unless required}}?:{{else}}:{{/unless}} {{{type}}};
458
+ {{/each}}
459
+ readonly repr: {
460
+ str: string;
461
+ img: string | null;
462
+ };
463
+
464
+ static configKey: string;
465
+ static modelName: string;
466
+ static primaryKeyField: string;
467
+ static relationshipFields: RelationshipFieldsMap;
468
+
469
+ // Use model-specific manager class instead of generic manager
470
+ static objects: {{className}}Manager;
471
+
472
+ constructor(data: Partial<{{interfaceName}}>);
473
+ serialize(): Partial<{{interfaceName}}>;
474
+ }
475
+
476
+ /**
477
+ * Runtime initialization
478
+ */
479
+ {{className}}.objects = new {{className}}Manager({{className}});
480
+ `;
481
+ // --------------------
482
+ // Handlebars Helpers
483
+ // --------------------
484
+ Handlebars.registerHelper("ifDefaultProvided", function (defaultValue, options) {
485
+ if (defaultValue !== "null") {
486
+ return options.fn(this);
487
+ }
488
+ else {
489
+ return options.inverse(this);
490
+ }
491
+ });
492
+ Handlebars.registerHelper("isRequired", function (required) {
493
+ return required ? "" : "?";
494
+ });
495
+ Handlebars.registerHelper("toLowerCase", function (str) {
496
+ return str.toLowerCase();
497
+ });
498
+ const jsTemplate = Handlebars.compile(JS_MODEL_TEMPLATE);
499
+ const dtsTemplate = Handlebars.compile(TS_DECLARATION_TEMPLATE);
500
+ // --------------------
501
+ // Core Generation Functions
502
+ // --------------------
503
+ /**
504
+ * Generates the schema for a given model.
505
+ * @param {BackendConfig} backend
506
+ * @param {string} model
507
+ * @returns {Promise<{model: string, relativePath: string}>}
508
+ */
509
+ async function generateSchemaForModel(backend, model) {
510
+ const schemaUrl = `${backend.API_URL}/${model}/get-schema/`;
511
+ const headers = backend.SYNC_TOKEN ? { "X-Sync-Token": backend.SYNC_TOKEN } : {};
512
+ const schemaResponse = await axios.get(schemaUrl, { headers });
513
+ /** @type {SchemaDefinition} */
514
+ let schema;
515
+ if (schemaResponse.data.components?.schemas?.[model]) {
516
+ schema = schemaResponse.data.components.schemas[model];
517
+ }
518
+ else if (schemaResponse.data.properties) {
519
+ schema = schemaResponse.data;
520
+ }
521
+ else {
522
+ console.error("Unexpected schema structure for model:", model);
523
+ throw new Error(`Invalid schema structure for model: ${model}`);
524
+ }
525
+ if (!schema.model_name) {
526
+ console.error(`Missing model_name attribute in schema for model: ${model}`);
527
+ process.exit(1);
528
+ }
529
+ const rawModelName = schema.model_name;
530
+ const className = schema.class_name;
531
+ const interfaceName = `${className}Fields`;
532
+ const parts = model.split(".");
533
+ const currentApp = parts.length > 1 ? parts[0] : "";
534
+ // With the backend folder added, we need an extra ../
535
+ const modulePath = process.env.NODE_ENV === "test" ? "../../../../src" : "@statezero/core";
536
+ const templateData = prepareTemplateData(modulePath, className, interfaceName, rawModelName, schema, currentApp, backend.NAME);
537
+ // Always include the backend key as the top-level folder
538
+ let outDir = path.join(backend.GENERATED_TYPES_DIR, backend.NAME);
539
+ if (parts.length > 1) {
540
+ outDir = path.join(outDir, ...parts.slice(0, -1).map((p) => p.toLowerCase()));
541
+ }
542
+ await fs.mkdir(outDir, { recursive: true });
543
+ const schemaFilePath = path.join(outDir, `${className.toLowerCase()}.schema.json`);
544
+ await fs.writeFile(schemaFilePath, JSON.stringify(schema, null, 2));
545
+ const jsContent = jsTemplate(templateData);
546
+ const baseName = parts[parts.length - 1].toLowerCase();
547
+ const jsFilePath = path.join(outDir, `${baseName}.js`);
548
+ await fs.writeFile(jsFilePath, jsContent);
549
+ const dtsContent = dtsTemplate(templateData);
550
+ const dtsFilePath = path.join(outDir, `${baseName}.d.ts`);
551
+ await fs.writeFile(dtsFilePath, dtsContent);
552
+ const relativePath = "./" +
553
+ path
554
+ .relative(backend.GENERATED_TYPES_DIR, jsFilePath)
555
+ .replace(/\\/g, "/")
556
+ .replace(/\.js$/, "");
557
+ return { model, relativePath, className };
558
+ }
559
+ /**
560
+ * Given a related model string (e.g. "django_app.deepmodellevel1"),
561
+ * extract the app label and model name to construct an import path.
562
+ * @param {string} currentApp
563
+ * @param {string} relModel
564
+ * @returns {string}
565
+ */
566
+ function getImportPath(currentApp, relModel) {
567
+ const parts = relModel.split(".");
568
+ const appLabel = parts[0];
569
+ const fileName = parts[parts.length - 1].toLowerCase();
570
+ return currentApp === appLabel
571
+ ? `./${fileName}`
572
+ : `../${appLabel}/${fileName}`;
573
+ }
574
+ /**
575
+ * Prepares template data for Handlebars.
576
+ * @param {string} modulePath
577
+ * @param {string} className
578
+ * @param {string} interfaceName
579
+ * @param {string} rawModelName
580
+ * @param {SchemaDefinition} schema
581
+ * @param {string} currentApp
582
+ * @param {string} configKey
583
+ * @returns {TemplateData}
584
+ */
585
+ function prepareTemplateData(modulePath, className, interfaceName, rawModelName, schema, currentApp, configKey) {
586
+ /** @type {PropertyDefinition[]} */
587
+ const properties = [];
588
+ /** @type {RelationshipField[]} */
589
+ const relationshipFields = [];
590
+ const usedDefs = new Set();
591
+ for (const [propName, prop] of Object.entries(schema.properties)) {
592
+ const propType = generateTypeForProperty(prop, schema.definitions, schema.relationships, propName);
593
+ const isRelationship = schema.relationships && schema.relationships[propName] !== undefined;
594
+ const isString = prop.type === "string";
595
+ const isNumber = prop.type === "integer" || prop.type === "number";
596
+ const isBoolean = prop.type === "boolean";
597
+ const isDate = prop.type === "string" && prop.format === "date-time";
598
+ const isPrimaryKey = schema.primary_key_field === propName;
599
+ const propDef = {
600
+ name: propName,
601
+ type: propType,
602
+ required: schema.required?.includes(propName) ?? false,
603
+ defaultValue: getDefaultValueForType(prop),
604
+ isRelationship,
605
+ isArrayRelationship: isRelationship
606
+ ? propType.startsWith("Array<")
607
+ : false,
608
+ isString,
609
+ isNumber,
610
+ isBoolean,
611
+ isDate,
612
+ isPrimaryKey,
613
+ };
614
+ if (isRelationship) {
615
+ const relData = schema.relationships[propName];
616
+ propDef.relationshipClassName = relData.class_name;
617
+ propDef.relationshipPrimaryKeyField = relData.primary_key_field;
618
+ // Add to relationshipFields array with modelName for dynamic loading
619
+ relationshipFields.push({
620
+ field: propName,
621
+ ModelClass: relData.class_name,
622
+ relationshipType: prop.format, // Use the format value directly
623
+ modelName: relData.model, // Add the full model name for getModelClass
624
+ });
625
+ }
626
+ properties.push(propDef);
627
+ const match = propType.match(/^(\w+)Fields$/);
628
+ if (match && schema.definitions && schema.definitions[match[1]]) {
629
+ usedDefs.add(match[1]);
630
+ }
631
+ const arrMatch = propType.match(/^Array<(\w+)Fields>$/);
632
+ if (arrMatch && schema.definitions && schema.definitions[arrMatch[1]]) {
633
+ usedDefs.add(arrMatch[1]);
634
+ }
635
+ }
636
+ const definitionsTs = [];
637
+ if (schema.definitions) {
638
+ for (const [defKey, defSchema] of Object.entries(schema.definitions)) {
639
+ if (!usedDefs.has(defKey))
640
+ continue;
641
+ let tsInterface = `export interface ${defKey}Fields {`;
642
+ const req = defSchema.required || [];
643
+ for (const [propName, prop] of Object.entries(defSchema.properties)) {
644
+ tsInterface += `\n ${propName}${req.includes(propName) ? "" : "?"}: ${generateTypeForProperty(prop, schema.definitions)};`;
645
+ }
646
+ tsInterface += `\n}`;
647
+ definitionsTs.push(tsInterface);
648
+ }
649
+ }
650
+ // Only add TypeScript definition imports - JS imports are handled dynamically
651
+ const tsImportSet = new Set();
652
+ if (schema.relationships) {
653
+ for (const [propName, rel] of Object.entries(schema.relationships)) {
654
+ const importPath = getImportPath(currentApp, rel.model);
655
+ tsImportSet.add(`import { ${rel.class_name}Fields, ${rel.class_name}QuerySet, ${rel.class_name}LiveQuerySet } from '${importPath}';`);
656
+ }
657
+ }
658
+ // Convert Sets to Arrays
659
+ const jsImports = []; // No static JS imports needed anymore
660
+ const tsImports = Array.from(tsImportSet);
661
+ let primaryKeyField = "id";
662
+ if (schema.primary_key_field !== undefined) {
663
+ primaryKeyField = schema.primary_key_field;
664
+ }
665
+ return {
666
+ modulePath,
667
+ className,
668
+ interfaceName,
669
+ modelName: rawModelName,
670
+ properties,
671
+ relationshipFields,
672
+ description: schema.description,
673
+ definitions: definitionsTs.length > 0 ? definitionsTs : undefined,
674
+ jsImports,
675
+ tsImports,
676
+ configKey,
677
+ primaryKeyField,
678
+ };
679
+ }
680
+ /**
681
+ * Generates a TypeScript type for a property.
682
+ * @param {SchemaProperty} prop
683
+ * @param {Object.<string, SchemaDefinition>} [definitions]
684
+ * @param {Object.<string, RelationshipData>} [relationships]
685
+ * @param {string} [propName]
686
+ * @returns {string}
687
+ */
688
+ function generateTypeForProperty(prop, definitions, relationships, propName) {
689
+ if (relationships && propName && relationships[propName]) {
690
+ const relData = relationships[propName];
691
+ const idType = prop.type === "integer" || prop.type === "number"
692
+ ? "number"
693
+ : prop.type === "string"
694
+ ? "string"
695
+ : "any";
696
+ return prop.format === "many-to-many"
697
+ ? `Array<${relData.class_name}Fields | ${idType}>`
698
+ : `${relData.class_name}Fields | ${idType}`;
699
+ }
700
+ if (prop.ref && prop.ref.startsWith("#/components/schemas/")) {
701
+ const defName = prop.ref.split("/").pop() || prop.ref;
702
+ return `${defName}Fields`;
703
+ }
704
+ if (prop.ref) {
705
+ return prop.format === "many-to-many"
706
+ ? `Array<${prop.ref}Fields>`
707
+ : `${prop.ref}Fields`;
708
+ }
709
+ let tsType;
710
+ switch (prop.type) {
711
+ case "string":
712
+ tsType = prop.enum
713
+ ? prop.enum.map((v) => `'${v}'`).join(" | ")
714
+ : "string";
715
+ break;
716
+ case "number":
717
+ case "integer":
718
+ tsType = "number";
719
+ break;
720
+ case "boolean":
721
+ tsType = "boolean";
722
+ break;
723
+ case "array":
724
+ tsType = prop.items
725
+ ? `Array<${generateTypeForProperty(prop.items, definitions)}>`
726
+ : "any[]";
727
+ break;
728
+ case "object":
729
+ if (prop.format === "json") {
730
+ tsType = "any";
731
+ }
732
+ else if (prop.properties) {
733
+ const nestedProps = Object.entries(prop.properties).map(([key, value]) => {
734
+ const isRequired = prop.required?.includes(key) ? "" : "?";
735
+ return `${key}${isRequired}: ${generateTypeForProperty(value, definitions)}`;
736
+ });
737
+ tsType = `{ ${nestedProps.join("; ")} }`;
738
+ }
739
+ else {
740
+ tsType = "Record<string, any>";
741
+ }
742
+ break;
743
+ default:
744
+ tsType = "any";
745
+ break;
746
+ }
747
+ if (prop.nullable) {
748
+ tsType = `${tsType} | null`;
749
+ }
750
+ return tsType;
751
+ }
752
+ /**
753
+ * Gets the default value for a property.
754
+ * @param {SchemaProperty} prop
755
+ * @returns {string}
756
+ */
757
+ function getDefaultValueForType(prop) {
758
+ return prop.default !== undefined ? JSON.stringify(prop.default) : "null";
759
+ }
760
+ /**
761
+ * Creates a unique alias for importing a model class based on backend, model path, and class name.
762
+ * Example: ('default', 'django_app.level1.deepmodel', 'DeepModel') => 'Default__django_app__level1__DeepModel'
763
+ * Ensures uniqueness by including sanitized path components.
764
+ *
765
+ * @param {string} backendKey - The backend configuration key (e.g., 'default', 'microservice').
766
+ * @param {string} modelName - The full model name string (e.g., 'django_app.level1.deepmodel').
767
+ * @param {string} className - The base class name (e.g., 'DeepModel').
768
+ * @returns {string} - A unique alias (e.g., 'Default__django_app__level1__DeepModel').
769
+ */
770
+ function generateImportAlias(backendKey, modelName, className) {
771
+ // 1. Sanitize Backend Key
772
+ const sanitizedBackendKey = (backendKey.charAt(0).toUpperCase() + backendKey.slice(1)).replace(/[^a-zA-Z0-9_]/g, ""); // Allow underscore
773
+ // 2. Sanitize Model Path Parts (all parts except the last, which relates to className)
774
+ const modelPathParts = modelName.split(".").slice(0, -1); // Get path parts like ['django_app', 'level1']
775
+ const sanitizedModelPath = modelPathParts
776
+ .map((part) => part.replace(/[^a-zA-Z0-9_]/g, "_")) // Replace invalid chars with underscore
777
+ .join("__"); // Join parts with double underscore -> "django_app__level1"
778
+ // 3. Combine: Backend__Path__ClassName
779
+ // Ensure className itself is sanitized just in case, although usually it should be a valid identifier
780
+ const sanitizedClassName = className.replace(/[^a-zA-Z0-9_]/g, "_");
781
+ if (sanitizedModelPath) {
782
+ // If path parts exist, include them: Default__django_app__level1__DeepModel
783
+ return `${sanitizedBackendKey}__${sanitizedModelPath}__${sanitizedClassName}`;
784
+ }
785
+ else {
786
+ // If no path parts (e.g., modelName is just 'simplemodel'), use: Default__SimpleModel
787
+ return `${sanitizedBackendKey}__${sanitizedClassName}`;
788
+ }
789
+ }
790
+ /**
791
+ * Generates a model registry file...
792
+ *
793
+ * @param {Array<{model: string, relativePath: string, backend: string, className: string}>} generatedFiles
794
+ * @param {Object.<string, BackendConfig>} backendConfigs
795
+ * @returns {Promise<void>}
796
+ */
797
+ async function generateModelRegistry(generatedFiles, backendConfigs) {
798
+ const registryByBackend = {};
799
+ const allUniqueImports = new Set();
800
+ for (const file of generatedFiles) {
801
+ const backendKey = file.backend;
802
+ const modelName = file.model; // e.g., 'django_app.dummymodel'
803
+ const originalClassName = file.className; // e.g., 'DummyModel'
804
+ const typesDir = backendConfigs[backendKey].GENERATED_TYPES_DIR;
805
+ const importPath = "./" +
806
+ path
807
+ .relative("./", path.join(typesDir, file.relativePath))
808
+ .replace(/\\/g, "/");
809
+ const importAlias = generateImportAlias(backendKey, modelName, originalClassName);
810
+ // Example result: 'Default__django_app__DummyModel'
811
+ registryByBackend[backendKey] = registryByBackend[backendKey] || {
812
+ imports: [],
813
+ models: {},
814
+ };
815
+ const importStatement = `import { ${originalClassName} as ${importAlias} } from '${importPath}.js';`;
816
+ registryByBackend[backendKey].imports.push(importStatement);
817
+ allUniqueImports.add(importStatement);
818
+ registryByBackend[backendKey].models[modelName] = importAlias; // Store mapping: 'django_app.dummymodel': 'Default__django_app__DummyModel'
819
+ }
820
+ // --- Generate registry content ---
821
+ let registryContent = `/**
822
+ * This file was auto-generated. Do not make direct changes to the file.
823
+ * It provides a registry of all models organized by config key and model name.
824
+ * Uses import aliases incorporating model paths to ensure uniqueness.
825
+ */
826
+
827
+ // --- Imports ---
828
+ `;
829
+ // Add all unique import statements collected
830
+ registryContent += Array.from(allUniqueImports).sort().join("\n");
831
+ registryContent += "\n\n";
832
+ // --- Create the registry object ---
833
+ registryContent += `/**
834
+ * Model registry mapped by configKey and modelName.
835
+ * Values are the aliased imported classes.
836
+ * @type {Object.<string, Object.<string, Function>>}
837
+ */
838
+ export const MODEL_REGISTRY = {
839
+ `;
840
+ // Add entries for each backend
841
+ const backendEntries = Object.entries(registryByBackend);
842
+ backendEntries.forEach(([backendKey, data], backendIndex) => {
843
+ registryContent += ` '${backendKey}': {\n`;
844
+ // Add model entries: 'model.name': Alias (e.g., 'django_app.dummymodel': Default__django_app__DummyModel)
845
+ const modelEntries = Object.entries(data.models);
846
+ modelEntries.forEach(([modelName, alias], modelIndex) => {
847
+ registryContent += ` '${modelName}': ${alias}`; // Use the generated alias here
848
+ if (modelIndex < modelEntries.length - 1) {
849
+ registryContent += ",";
850
+ }
851
+ registryContent += "\n";
852
+ });
853
+ registryContent += ` }`;
854
+ if (backendIndex < backendEntries.length - 1) {
855
+ registryContent += ",";
856
+ }
857
+ registryContent += "\n";
858
+ });
859
+ registryContent += `};
860
+
861
+ /**
862
+ * Get a model class by name.
863
+ * This remains synchronous.
864
+ *
865
+ * @param {string} modelName - The model name (e.g., 'django_app.dummymodel')
866
+ * @param {string} configKey - The config key (backend name, e.g., 'default')
867
+ * @returns {Function|null} - The model class (via its alias) or null if not found.
868
+ */
869
+ export function getModelClass(modelName, configKey) {
870
+ if (MODEL_REGISTRY[configKey] && MODEL_REGISTRY[configKey][modelName]) {
871
+ // Returns the specific aliased class for that backend/model combination
872
+ return MODEL_REGISTRY[configKey][modelName];
873
+ }
874
+
875
+ console.warn(\`Model class not found for '\${modelName}' in config '\${configKey}'\`);
876
+ return null;
877
+ }
878
+ `;
879
+ // --- Write the file ---
880
+ const rootDir = process.cwd();
881
+ const registryFilePath = path.join(rootDir, "model-registry.js");
882
+ try {
883
+ await fs.writeFile(registryFilePath, registryContent);
884
+ console.log(`✨ Generated model registry with path-based aliases at ${registryFilePath}`);
885
+ }
886
+ catch (err) {
887
+ console.error(`❌ Failed to write model registry at ${registryFilePath}:`, err);
888
+ }
889
+ }
890
+ /**
891
+ * Generates app-level index files and a root index file that imports from app indexes.
892
+ * @param {Array<{model: string, relativePath: string, backend: string}>} generatedFiles
893
+ * @param {Object.<string, BackendConfig>} backendConfigs
894
+ * @returns {Promise<void>}
895
+ */
896
+ async function generateAppLevelIndexFiles(generatedFiles, backendConfigs) {
897
+ // Group files by backend and app
898
+ const filesByBackendAndApp = generatedFiles.reduce((acc, file) => {
899
+ const backend = file.backend;
900
+ const parts = file.model.split(".");
901
+ const app = parts.length > 1 ? parts[0] : "root"; // Use 'root' for models without an app
902
+ acc[backend] = acc[backend] || {};
903
+ acc[backend][app] = acc[backend][app] || [];
904
+ acc[backend][app].push(file);
905
+ return acc;
906
+ }, {});
907
+ const indexTemplate = Handlebars.compile(`{{#each files}}
908
+ export * from '{{this.relativePath}}';
909
+ {{/each}}`);
910
+ // Generate app-level index files for each backend and app
911
+ for (const [backendName, appGroups] of Object.entries(filesByBackendAndApp)) {
912
+ const backend = backendConfigs[backendName];
913
+ const rootExports = [];
914
+ for (const [app, files] of Object.entries(appGroups)) {
915
+ if (app === "root") {
916
+ // Handle models without an app prefix
917
+ for (const file of files) {
918
+ rootExports.push(`export * from '${file.relativePath}';`);
919
+ }
920
+ }
921
+ else {
922
+ // Create app-level index files with proper relative paths
923
+ // Include backend key in the path
924
+ const appDir = path.join(backend.GENERATED_TYPES_DIR, backend.NAME, app.toLowerCase());
925
+ // Create relative paths for imports within the app directory
926
+ // These should be relative to the app directory, not the backend root
927
+ const appFiles = files.map((file) => {
928
+ // Get the last part of the path (the actual file name without extension)
929
+ const fileName = path.basename(file.relativePath);
930
+ return {
931
+ ...file,
932
+ relativePath: "./" + fileName,
933
+ };
934
+ });
935
+ const indexContent = indexTemplate({ files: appFiles });
936
+ await fs.writeFile(path.join(appDir, "index.js"), indexContent.trim());
937
+ await fs.writeFile(path.join(appDir, "index.d.ts"), indexContent.trim());
938
+ // Add an export for this app's index to the backend root index
939
+ rootExports.push(`export * from './${app.toLowerCase()}/index.js';`);
940
+ }
941
+ }
942
+ // Write the backend root index file (add FileObject export)
943
+ const backendIndexContent = [
944
+ "export * from './fileobject.js';", // Add this line
945
+ ...rootExports,
946
+ ].join("\n");
947
+ // Include backend key in the path for index files
948
+ const backendDir = path.join(backend.GENERATED_TYPES_DIR, backend.NAME);
949
+ await fs.writeFile(path.join(backendDir, "index.js"), backendIndexContent);
950
+ await fs.writeFile(path.join(backendDir, "index.d.ts"), backendIndexContent);
951
+ }
952
+ }
953
+ // FileObject template
954
+ const FILEOBJECT_TEMPLATE = `/**
955
+ * This file was auto-generated. Do not make direct changes to the file.
956
+ * Backend-specific FileObject class for {{backendName}}
957
+ */
958
+
959
+ import { FileObject as BaseFileObject } from '{{modulePath}}';
960
+
961
+ export class {{backendName}}FileObject extends BaseFileObject {
962
+ static configKey = '{{backendName}}';
963
+ }
964
+
965
+ export const FileObject = {{backendName}}FileObject;
966
+ `;
967
+ const fileObjectTemplate = Handlebars.compile(FILEOBJECT_TEMPLATE);
968
+ /**
969
+ * Generates a FileObject class for a backend.
970
+ * @param {BackendConfig} backend
971
+ * @returns {Promise<void>}
972
+ */
973
+ async function generateFileObjectForBackend(backend) {
974
+ const modulePath = process.env.NODE_ENV === "test" ? "../../../src" : "@statezero/core";
975
+ const templateData = {
976
+ backendName: backend.NAME,
977
+ modulePath: modulePath,
978
+ };
979
+ const fileObjectContent = fileObjectTemplate(templateData);
980
+ // Include backend key in the path
981
+ const backendDir = path.join(backend.GENERATED_TYPES_DIR, backend.NAME);
982
+ await fs.mkdir(backendDir, { recursive: true });
983
+ const fileObjectPath = path.join(backendDir, "fileobject.js");
984
+ await fs.writeFile(fileObjectPath, fileObjectContent);
985
+ // Also generate TypeScript declaration
986
+ const dtsContent = `/**
987
+ * This file was auto-generated. Do not make direct changes to the file.
988
+ * Backend-specific FileObject class for ${backend.NAME}
989
+ */
990
+
991
+ import { FileObject as BaseFileObject } from '${modulePath}';
992
+
993
+ export declare class ${backend.NAME}FileObject extends BaseFileObject {
994
+ static configKey: string;
995
+ }
996
+
997
+ export declare const FileObject: typeof ${backend.NAME}FileObject;
998
+ `;
999
+ const dtsPath = path.join(backendDir, "fileobject.d.ts");
1000
+ await fs.writeFile(dtsPath, dtsContent);
1001
+ }
1002
+ /**
1003
+ * Generates a top-level index file that re-exports from the 'default' backend
1004
+ * if it exists. This improves backwards compatibility and reduces verbosity.
1005
+ * @param {Object.<string, BackendConfig>} backendConfigs
1006
+ * @returns {Promise<void>}
1007
+ */
1008
+ async function generateDefaultBackendIndex(backendConfigs) {
1009
+ // Check if a 'default' backend exists
1010
+ if (!backendConfigs['default']) {
1011
+ return; // No default backend, skip
1012
+ }
1013
+ const defaultBackend = backendConfigs['default'];
1014
+ const typesDir = defaultBackend.GENERATED_TYPES_DIR;
1015
+ // Create top-level index files that re-export from default
1016
+ const indexContent = `/**
1017
+ * This file was auto-generated. Do not make direct changes to the file.
1018
+ * Re-exports from the 'default' backend for backwards compatibility.
1019
+ */
1020
+
1021
+ export * from './default/index.js';
1022
+ `;
1023
+ const jsIndexPath = path.join(typesDir, 'index.js');
1024
+ const dtsIndexPath = path.join(typesDir, 'index.d.ts');
1025
+ await fs.writeFile(jsIndexPath, indexContent);
1026
+ await fs.writeFile(dtsIndexPath, indexContent);
1027
+ console.log(`✨ Generated top-level index for 'default' backend`);
1028
+ }
1029
+ // --------------------
1030
+ // Main Runner: Fetch models and prompt selection
1031
+ // --------------------
1032
+ async function main() {
1033
+ // Load configuration from file (CLI-only or tests) before any other operations.
1034
+ loadConfigFromFile();
1035
+ // Retrieve the validated configuration from the global config singleton.
1036
+ const configData = configInstance.getConfig();
1037
+ const backendConfigs = configData.backendConfigs;
1038
+ const fetchPromises = Object.keys(backendConfigs).map(async (key) => {
1039
+ const backend = backendConfigs[key];
1040
+ backend.NAME = key;
1041
+ try {
1042
+ const headers = backend.SYNC_TOKEN ? { "X-Sync-Token": backend.SYNC_TOKEN } : {};
1043
+ const response = await axios.get(`${backend.API_URL}/models/`, { headers });
1044
+ return { backend, models: response.data };
1045
+ }
1046
+ catch (error) {
1047
+ console.error(`Error fetching models from backend ${backend.NAME}:`, error.message);
1048
+ return { backend, models: [] };
1049
+ }
1050
+ });
1051
+ const backendModels = await Promise.all(fetchPromises);
1052
+ const choices = [];
1053
+ // Create a simple separator object for environments where inquirer.Separator isn't available
1054
+ const createSeparator = (text) => ({ name: text, value: null });
1055
+ for (const { backend, models } of backendModels) {
1056
+ choices.push(createSeparator(`\n=== ${backend.NAME} ===\n`));
1057
+ for (const model of models) {
1058
+ choices.push({
1059
+ name: model,
1060
+ value: { backend, model },
1061
+ checked: true,
1062
+ });
1063
+ }
1064
+ }
1065
+ if (choices.length === 0) {
1066
+ console.log("No models to synchronise");
1067
+ process.exit(0);
1068
+ }
1069
+ const selectedModels = await selectModels(choices, "Select models to synchronise:");
1070
+ if (!selectedModels || selectedModels.length === 0) {
1071
+ console.log("No models selected. Exiting.");
1072
+ process.exit(0);
1073
+ }
1074
+ const modelsByBackend = selectedModels.reduce((acc, item) => {
1075
+ const key = item.backend.NAME;
1076
+ acc[key] = acc[key] || { backend: item.backend, models: [] };
1077
+ acc[key].models.push(item.model);
1078
+ return acc;
1079
+ }, {});
1080
+ const allGeneratedFiles = [];
1081
+ for (const group of Object.values(modelsByBackend)) {
1082
+ console.log(`\nProcessing backend: ${group.backend.NAME}`);
1083
+ const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
1084
+ progressBar.start(group.models.length, 0);
1085
+ for (const model of group.models) {
1086
+ try {
1087
+ const result = await generateSchemaForModel(group.backend, model);
1088
+ allGeneratedFiles.push({ ...result, backend: group.backend.NAME });
1089
+ }
1090
+ catch (error) {
1091
+ console.error(`Error generating schema for model ${model} from backend ${group.backend.NAME}:`, error.message);
1092
+ }
1093
+ progressBar.increment();
1094
+ }
1095
+ progressBar.stop();
1096
+ // Generate FileObject for this backend
1097
+ try {
1098
+ await generateFileObjectForBackend(group.backend);
1099
+ console.log(`✨ Generated FileObject for backend: ${group.backend.NAME}`);
1100
+ }
1101
+ catch (error) {
1102
+ console.error(`Error generating FileObject for backend ${group.backend.NAME}:`, error.message);
1103
+ }
1104
+ }
1105
+ // Generate an index file per app
1106
+ await generateAppLevelIndexFiles(allGeneratedFiles, backendConfigs);
1107
+ // Generate model registry
1108
+ await generateModelRegistry(allGeneratedFiles, backendConfigs);
1109
+ // Generate top-level index for 'default' backend if it exists
1110
+ await generateDefaultBackendIndex(backendConfigs);
1111
+ console.log(`✨ Generated JavaScript files with TypeScript declarations for ${selectedModels.length} models across ${Object.keys(backendConfigs).length} backends.`);
1112
+ }
1113
+ /**
1114
+ * Main exported function to generate schema.
1115
+ * @param {GenerateArgs} args
1116
+ * @returns {Promise<void>}
1117
+ */
1118
+ export async function generateSchema(args) {
1119
+ await main();
1120
+ }