@statezero/core 0.2.38 → 0.2.40

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 +348 -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 +51 -0
  287. package/dist/syncEngine/sync.js +419 -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,366 @@
1
+ import { Manager } from "./manager.js";
2
+ import { ValidationError } from "./errors.js";
3
+ import { modelStoreRegistry } from "../../syncEngine/registries/modelStoreRegistry.js";
4
+ import { isNil } from "lodash-es";
5
+ import { QueryExecutor } from "./queryExecutor.js";
6
+ import { wrapReactiveModel } from "../../reactiveAdaptor.js";
7
+ import { DateParsingHelpers } from "./dates.js";
8
+ import { FileObject } from './files.js';
9
+ import { configInstance } from "../../config.js";
10
+ import { ModelSerializer } from "./serializers.js";
11
+ import { parseStateZeroError, MultipleObjectsReturned, DoesNotExist, } from "./errors.js";
12
+ import axios from "axios";
13
+ /**
14
+ * A constructor for a Model.
15
+ *
16
+ * @typedef {Function} ModelConstructor
17
+ * @param {any} data - Data to initialize the model.
18
+ * @returns {Model}
19
+ *
20
+ * @property {Manager} objects - The model's manager.
21
+ * @property {string} configKey - The configuration key.
22
+ * @property {string} modelName - The model name.
23
+ * @property {string} primaryKeyField - The primary key field (default 'id').
24
+ */
25
+ /**
26
+ * Base Model class with integrated API implementation.
27
+ *
28
+ * @abstract
29
+ */
30
+ export class Model {
31
+ constructor(data = {}) {
32
+ this.serializer = new ModelSerializer(this.constructor);
33
+ const serializedData = this.serializer.toInternal(data);
34
+ this._data = serializedData;
35
+ this._pk = serializedData[this.constructor.primaryKeyField] || undefined;
36
+ this.__version = 0;
37
+ return wrapReactiveModel(this);
38
+ }
39
+ /**
40
+ * Remote-only manager that skips local store updates.
41
+ */
42
+ static get remote() {
43
+ return this.objects.remote();
44
+ }
45
+ touch() {
46
+ this.__version++;
47
+ }
48
+ /**
49
+ * Returns the primary key of the model instance.
50
+ *
51
+ * @returns {number|undefined} The primary key.
52
+ */
53
+ get pk() {
54
+ return this._pk;
55
+ }
56
+ /**
57
+ * Sets the primary key of the model instance.
58
+ *
59
+ * @param {number|undefined} value - The new primary key value.
60
+ */
61
+ set pk(value) {
62
+ this._pk = value;
63
+ this.touch();
64
+ }
65
+ /**
66
+ * Instantiate from pk using queryset scoped singletons
67
+ */
68
+ static fromPk(pk, querySet) {
69
+ let qsId = querySet ? querySet.__uuid : "";
70
+ let key = `${qsId}__${this.configKey}__${this.modelName}__${pk}`;
71
+ if (!this.instanceCache.has(key)) {
72
+ const instance = new this();
73
+ instance.pk = pk;
74
+ this.instanceCache.set(key, instance);
75
+ }
76
+ return this.instanceCache.get(key);
77
+ }
78
+ /**
79
+ * Gets a field value from the internal data store
80
+ *
81
+ * @param {string} field - The field name
82
+ * @returns {any} The field value
83
+ */
84
+ getField(field) {
85
+ // Access the reactive __version property to establish dependency for vue integration
86
+ const trackVersion = this.__version;
87
+ const ModelClass = this.constructor;
88
+ if (ModelClass.primaryKeyField === field)
89
+ return this._pk;
90
+ // check local overrides
91
+ let value = this._data[field];
92
+ // if its not been overridden, get it from the store
93
+ if (value === undefined && !isNil(this._pk)) {
94
+ let storedValue = modelStoreRegistry.getEntity(ModelClass, this._pk);
95
+ if (storedValue)
96
+ value = storedValue[field]; // if stops null -> undefined
97
+ }
98
+ // Use serializer to convert internal format to live format
99
+ return this.serializer.toLiveField(field, value);
100
+ }
101
+ /**
102
+ * Sets a field value in the internal data store
103
+ *
104
+ * @param {string} field - The field name
105
+ * @param {any} value - The field value to set
106
+ */
107
+ setField(field, value) {
108
+ const ModelClass = this.constructor;
109
+ // Use serializer to convert live format to internal format
110
+ const internalValue = this.serializer.toInternalField(field, value);
111
+ if (ModelClass.primaryKeyField === field) {
112
+ this._pk = internalValue;
113
+ }
114
+ else {
115
+ this._data[field] = internalValue;
116
+ }
117
+ }
118
+ /**
119
+ * Validates that the provided data object only contains keys
120
+ * defined in the model's allowed fields. Supports nested fields
121
+ * using double underscore notation (e.g., author__name).
122
+ *
123
+ * @param {Object} data - The object to validate.
124
+ * @throws {ValidationError} If an unknown key is found.
125
+ */
126
+ static validateFields(data) {
127
+ if (isNil(data))
128
+ return;
129
+ const allowedFields = this.fields;
130
+ for (const key of Object.keys(data)) {
131
+ if (key === "repr" || key === "type")
132
+ continue;
133
+ // Handle nested fields by splitting on double underscore
134
+ // and taking just the base field name
135
+ const baseField = key.split("__")[0];
136
+ if (!allowedFields.includes(baseField)) {
137
+ let errorMsg = `Invalid field: ${baseField}. Allowed fields are: ${allowedFields.join(", ")}`;
138
+ console.error(errorMsg);
139
+ throw new ValidationError(errorMsg);
140
+ }
141
+ }
142
+ }
143
+ /**
144
+ * Serializes the model instance.
145
+ *
146
+ * By default, it returns all enumerable own properties.
147
+ * Subclasses should override this to return specific keys.
148
+ *
149
+ * @param {boolean} includeRepr - Whether to include the repr field (for caching). Default: false.
150
+ * @returns {Object} The serialized model data.
151
+ */
152
+ serialize(includeRepr = false) {
153
+ const ModelClass = this.constructor;
154
+ const data = {};
155
+ // Collect all field values (already in internal format)
156
+ for (const field of ModelClass.fields) {
157
+ if (field === ModelClass.primaryKeyField) {
158
+ data[field] = this._pk;
159
+ }
160
+ else {
161
+ let value = this._data[field];
162
+ // Get from store if not in local data
163
+ if (value === undefined && !isNil(this._pk)) {
164
+ const storedData = modelStoreRegistry.getEntity(ModelClass, this._pk);
165
+ if (storedData) {
166
+ value = storedData[field];
167
+ }
168
+ }
169
+ data[field] = value;
170
+ }
171
+ }
172
+ // Include repr field if requested (for caching purposes)
173
+ if (includeRepr && !isNil(this._pk)) {
174
+ const storedData = modelStoreRegistry.getEntity(ModelClass, this._pk);
175
+ if (storedData && storedData.repr) {
176
+ data.repr = storedData.repr;
177
+ }
178
+ }
179
+ // Data is already in internal format, so return as-is for API transmission
180
+ return data;
181
+ }
182
+ /**
183
+ * Saves the model instance by either creating a new record or updating an existing one.
184
+ *
185
+ * @returns {Promise<Model>} A promise that resolves to the updated model instance.
186
+ */
187
+ async save() {
188
+ const ModelClass = this.constructor;
189
+ const pkField = ModelClass.primaryKeyField;
190
+ const querySet = !this.pk
191
+ ? ModelClass.objects.newQuerySet()
192
+ : ModelClass.objects.filter({ [pkField]: this.pk });
193
+ const data = this.serialize();
194
+ let instance;
195
+ if (!this.pk) {
196
+ // Create new instance
197
+ instance = await QueryExecutor.execute(querySet, "create", { data });
198
+ }
199
+ else {
200
+ // Update existing instance
201
+ instance = await QueryExecutor.execute(querySet, "update_instance", {
202
+ data,
203
+ });
204
+ }
205
+ this._pk = instance.pk;
206
+ this._data = {};
207
+ return this;
208
+ }
209
+ /**
210
+ * Deletes the instance from the database.
211
+ *
212
+ * Returns a tuple with the number of objects deleted and an object mapping
213
+ * model names to the number of objects deleted, matching Django's behavior.
214
+ *
215
+ * @returns {Promise<[number, Object]>} A promise that resolves to the deletion result.
216
+ * @throws {Error} If the instance has not been saved (no primary key).
217
+ */
218
+ async delete() {
219
+ if (!this.pk) {
220
+ throw new Error("Cannot delete unsaved instance");
221
+ }
222
+ const ModelClass = this.constructor;
223
+ const pkField = ModelClass.primaryKeyField;
224
+ const querySet = ModelClass.objects.filter({ [pkField]: this.pk });
225
+ // Pass the instance data with primary key as the args
226
+ const args = { [pkField]: this.pk };
227
+ const result = await QueryExecutor.execute(querySet, "delete_instance", args);
228
+ // result -> [deletedCount, { [modelName]: deletedCount }];
229
+ return result;
230
+ }
231
+ /**
232
+ * Refreshes the model instance with data from the database.
233
+ *
234
+ * @returns {Promise<void>} A promise that resolves when the instance has been refreshed.
235
+ * @throws {Error} If the instance has not been saved (no primary key).
236
+ */
237
+ async refreshFromDb() {
238
+ if (!this.pk) {
239
+ throw new Error("Cannot refresh unsaved instance");
240
+ }
241
+ const ModelClass = this.constructor;
242
+ const fresh = await ModelClass.objects.get({
243
+ [ModelClass.primaryKeyField]: this.pk,
244
+ });
245
+ // clear the current data and fresh data will flow
246
+ this._data = {};
247
+ }
248
+ /**
249
+ * Validates the model instance using the same serialize behavior as save()
250
+ * @param {string} validateType - 'create' or 'update' (defaults to auto-detect)
251
+ * @param {boolean} partial - Whether to allow partial validation
252
+ * @returns {Promise<boolean>} Promise that resolves to true if valid, throws error if invalid
253
+ */
254
+ async validate(validateType = null, partial = false) {
255
+ const ModelClass = this.constructor;
256
+ if (!validateType) {
257
+ validateType = this.pk ? "update" : "create";
258
+ }
259
+ // Validate the validateType parameter
260
+ if (!["update", "create"].includes(validateType)) {
261
+ throw new Error(`Validation type must be 'update' or 'create', not '${validateType}'`);
262
+ }
263
+ // Use the same serialize logic as save()
264
+ const data = this.serialize();
265
+ // Delegate to static method
266
+ return ModelClass.validate(data, validateType, partial);
267
+ }
268
+ /**
269
+ * Static method to validate data without creating an instance
270
+ * @param {Object} data - Data to validate
271
+ * @param {string} validateType - 'create' or 'update'
272
+ * @param {boolean} partial - Whether to allow partial validation
273
+ * @returns {Promise<boolean>} Promise that resolves to true if valid, throws error if invalid
274
+ */
275
+ static async validate(data, validateType = "create", partial = false) {
276
+ const ModelClass = this;
277
+ // Validate the validateType parameter
278
+ if (!["update", "create"].includes(validateType)) {
279
+ throw new Error(`Validation type must be 'update' or 'create', not '${validateType}'`);
280
+ }
281
+ // Get backend config and check if it exists
282
+ const config = configInstance.getConfig();
283
+ const backend = config.backendConfigs[ModelClass.configKey];
284
+ if (!backend) {
285
+ throw new Error(`No backend configuration found for key: ${ModelClass.configKey}`);
286
+ }
287
+ // Build URL for validate endpoint
288
+ const baseUrl = backend.API_URL.replace(/\/+$/, "");
289
+ const url = `${baseUrl}/${ModelClass.modelName}/validate/`;
290
+ // Prepare headers
291
+ const headers = {
292
+ "Content-Type": "application/json",
293
+ ...(backend.getAuthHeaders ? backend.getAuthHeaders() : {}),
294
+ };
295
+ // Make direct API call to validate endpoint
296
+ try {
297
+ const response = await axios.post(url, {
298
+ data: data,
299
+ validate_type: validateType,
300
+ partial: partial,
301
+ }, { headers });
302
+ // Backend returns {"valid": true} on success
303
+ return response.data.valid === true;
304
+ }
305
+ catch (error) {
306
+ if (error.response && error.response.data) {
307
+ const parsedError = parseStateZeroError(error.response.data);
308
+ if (Error.captureStackTrace) {
309
+ Error.captureStackTrace(parsedError, ModelClass.validate);
310
+ }
311
+ throw parsedError;
312
+ }
313
+ throw new Error(`Validation failed: ${error.message}`);
314
+ }
315
+ }
316
+ /**
317
+ * Get field permissions for the current user (cached on the class)
318
+ * @param {boolean} refresh - Force refresh the cached permissions
319
+ * @returns {Promise<{visible_fields: string[], creatable_fields: string[], editable_fields: string[]}>}
320
+ */
321
+ static async getFieldPermissions(refresh = false) {
322
+ const ModelClass = this;
323
+ // Return cached permissions if available and not forcing refresh
324
+ if (!refresh && ModelClass._fieldPermissionsCache) {
325
+ return ModelClass._fieldPermissionsCache;
326
+ }
327
+ // Get backend config and check if it exists
328
+ const config = configInstance.getConfig();
329
+ const backend = config.backendConfigs[ModelClass.configKey];
330
+ if (!backend) {
331
+ throw new Error(`No backend configuration found for key: ${ModelClass.configKey}`);
332
+ }
333
+ // Build URL for field permissions endpoint
334
+ const baseUrl = backend.API_URL.replace(/\/+$/, "");
335
+ const url = `${baseUrl}/${ModelClass.modelName}/field-permissions/`;
336
+ // Prepare headers
337
+ const headers = {
338
+ "Content-Type": "application/json",
339
+ ...(backend.getAuthHeaders ? backend.getAuthHeaders() : {}),
340
+ };
341
+ // Make direct API call to field permissions endpoint
342
+ try {
343
+ const response = await axios.get(url, { headers });
344
+ // Cache the permissions on the class
345
+ ModelClass._fieldPermissionsCache = response.data;
346
+ // Backend returns {visible_fields: [], creatable_fields: [], editable_fields: []}
347
+ return response.data;
348
+ }
349
+ catch (error) {
350
+ if (error.response && error.response.data) {
351
+ const parsedError = parseStateZeroError(error.response.data);
352
+ if (Error.captureStackTrace) {
353
+ Error.captureStackTrace(parsedError, ModelClass.getFieldPermissions);
354
+ }
355
+ throw parsedError;
356
+ }
357
+ throw new Error(`Failed to get field permissions: ${error.message}`);
358
+ }
359
+ }
360
+ }
361
+ /**
362
+ * Creates a new Model instance.
363
+ *
364
+ * @param {any} [data={}] - The data for initialization.
365
+ */
366
+ Model.instanceCache = new Map();
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Factory for creating Operation instances with consistent behavior
3
+ * across QueryExecutor and hotpath event handling
4
+ */
5
+ export class OperationFactory {
6
+ /**
7
+ * Create a CREATE operation
8
+ * @param {QuerySet} queryset - The queryset context
9
+ * @param {Object} data - The data for the new instance
10
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
11
+ * @returns {Operation} The created operation
12
+ */
13
+ static createCreateOperation(queryset: QuerySet, data?: Object, operationId?: string): Operation;
14
+ /**
15
+ * Create a BULK_CREATE operation
16
+ * @param {QuerySet} queryset - The queryset context
17
+ * @param {Array<Object>} dataList - Array of data objects for the new instances
18
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
19
+ * @returns {Operation} The created operation
20
+ */
21
+ static createBulkCreateOperation(queryset: QuerySet, dataList?: Array<Object>, operationId?: string): Operation;
22
+ /**
23
+ * Create an UPDATE operation with optimistic instance updates
24
+ * @param {QuerySet} queryset - The queryset context
25
+ * @param {Object} data - The update data
26
+ * @param {Object} filter - Optional filter for the update
27
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
28
+ * @returns {Operation} The created operation
29
+ */
30
+ static createUpdateOperation(queryset: QuerySet, data?: Object, filter?: Object, operationId?: string): Operation;
31
+ /**
32
+ * Create a DELETE operation
33
+ * @param {QuerySet} queryset - The queryset context
34
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
35
+ * @returns {Operation} The created operation
36
+ */
37
+ static createDeleteOperation(queryset: QuerySet, operationId?: string): Operation;
38
+ /**
39
+ * Create an UPDATE_INSTANCE operation
40
+ * @param {QuerySet} queryset - The queryset context
41
+ * @param {Object} data - The update data
42
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
43
+ * @returns {Operation} The created operation
44
+ */
45
+ static createUpdateInstanceOperation(queryset: QuerySet, data?: Object, operationId?: string): Operation;
46
+ /**
47
+ * Create a DELETE_INSTANCE operation
48
+ * @param {QuerySet} queryset - The queryset context
49
+ * @param {Object} instanceData - The instance to delete (object with PK)
50
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
51
+ * @returns {Operation} The created operation
52
+ */
53
+ static createDeleteInstanceOperation(queryset: QuerySet, instanceData: Object, operationId?: string): Operation;
54
+ /**
55
+ * Create a GET_OR_CREATE operation with local filtering logic
56
+ * @param {QuerySet} queryset - The queryset context
57
+ * @param {Object} lookup - The lookup criteria
58
+ * @param {Object} defaults - The default values for creation
59
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
60
+ * @returns {Operation} The created operation
61
+ */
62
+ static createGetOrCreateOperation(queryset: QuerySet, lookup?: Object, defaults?: Object, operationId?: string): Operation;
63
+ /**
64
+ * Create an UPDATE_OR_CREATE operation with local filtering logic
65
+ * @param {QuerySet} queryset - The queryset context
66
+ * @param {Object} lookup - The lookup criteria
67
+ * @param {Object} defaults - The default values for creation/update
68
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
69
+ * @returns {Operation} The created operation
70
+ */
71
+ static createUpdateOrCreateOperation(queryset: QuerySet, lookup?: Object, defaults?: Object, operationId?: string): Operation;
72
+ }
73
+ import { Operation } from '../../syncEngine/stores/operation.js';
@@ -0,0 +1,248 @@
1
+ import { Operation, Type, Status } from '../../syncEngine/stores/operation.js';
2
+ import { v7 as uuid7 } from 'uuid';
3
+ import { createTempPk } from './tempPk.js';
4
+ import { getRequiredFields, pickRequiredFields, processQuery } from '../../filtering/localFiltering.js';
5
+ import { evaluateExpression } from './f.js';
6
+ import { modelStoreRegistry } from '../../syncEngine/registries/modelStoreRegistry.js';
7
+ import { querysetStoreRegistry } from '../../syncEngine/registries/querysetStoreRegistry.js';
8
+ import { isNil } from 'lodash-es';
9
+ /**
10
+ * Factory for creating Operation instances with consistent behavior
11
+ * across QueryExecutor and hotpath event handling
12
+ */
13
+ export class OperationFactory {
14
+ /**
15
+ * Create a CREATE operation
16
+ * @param {QuerySet} queryset - The queryset context
17
+ * @param {Object} data - The data for the new instance
18
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
19
+ * @returns {Operation} The created operation
20
+ */
21
+ static createCreateOperation(queryset, data = {}, operationId = null) {
22
+ const ModelClass = queryset.ModelClass;
23
+ const primaryKeyField = ModelClass.primaryKeyField;
24
+ const opId = operationId || `${uuid7()}`;
25
+ const tempPk = createTempPk(opId);
26
+ return new Operation({
27
+ operationId: opId,
28
+ type: Type.CREATE,
29
+ instances: [{ ...data, [primaryKeyField]: tempPk }],
30
+ queryset: queryset,
31
+ args: { data },
32
+ localOnly: queryset._optimisticOnly || false,
33
+ });
34
+ }
35
+ /**
36
+ * Create a BULK_CREATE operation
37
+ * @param {QuerySet} queryset - The queryset context
38
+ * @param {Array<Object>} dataList - Array of data objects for the new instances
39
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
40
+ * @returns {Operation} The created operation
41
+ */
42
+ static createBulkCreateOperation(queryset, dataList = [], operationId = null) {
43
+ const ModelClass = queryset.ModelClass;
44
+ const primaryKeyField = ModelClass.primaryKeyField;
45
+ const opId = operationId || `${uuid7()}`;
46
+ // Create temp PKs for each instance
47
+ const instances = dataList.map((data, index) => {
48
+ const tempPk = createTempPk(`${opId}_${index}`);
49
+ return { ...data, [primaryKeyField]: tempPk };
50
+ });
51
+ return new Operation({
52
+ operationId: opId,
53
+ type: Type.BULK_CREATE,
54
+ instances: instances,
55
+ queryset: queryset,
56
+ args: { data: dataList },
57
+ localOnly: queryset._optimisticOnly || false,
58
+ });
59
+ }
60
+ /**
61
+ * Create an UPDATE operation with optimistic instance updates
62
+ * @param {QuerySet} queryset - The queryset context
63
+ * @param {Object} data - The update data
64
+ * @param {Object} filter - Optional filter for the update
65
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
66
+ * @returns {Operation} The created operation
67
+ */
68
+ static createUpdateOperation(queryset, data = {}, filter = null, operationId = null) {
69
+ const ModelClass = queryset.ModelClass;
70
+ const primaryKeyField = ModelClass.primaryKeyField;
71
+ const store = querysetStoreRegistry.getStore(queryset);
72
+ const querysetPks = store.render();
73
+ const opId = operationId || `${uuid7()}`;
74
+ // Create optimistic instances with F expression evaluation
75
+ const optimisticInstances = querysetPks.map(pk => {
76
+ const instance = modelStoreRegistry.getEntity(ModelClass, pk);
77
+ const updatedInstance = { ...instance };
78
+ updatedInstance[primaryKeyField] = pk;
79
+ for (const [key, value] of Object.entries(data)) {
80
+ if (value && typeof value === 'object' && value.__f_expr) {
81
+ const evaluatedValue = evaluateExpression(value, instance);
82
+ if (evaluatedValue !== null) {
83
+ updatedInstance[key] = evaluatedValue;
84
+ }
85
+ else {
86
+ updatedInstance[key] = instance[key];
87
+ }
88
+ }
89
+ else {
90
+ updatedInstance[key] = value;
91
+ }
92
+ }
93
+ return updatedInstance;
94
+ });
95
+ return new Operation({
96
+ operationId: opId,
97
+ type: Type.UPDATE,
98
+ instances: optimisticInstances,
99
+ queryset: queryset,
100
+ args: { filter, data },
101
+ localOnly: queryset._optimisticOnly || false,
102
+ });
103
+ }
104
+ /**
105
+ * Create a DELETE operation
106
+ * @param {QuerySet} queryset - The queryset context
107
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
108
+ * @returns {Operation} The created operation
109
+ */
110
+ static createDeleteOperation(queryset, operationId = null) {
111
+ const ModelClass = queryset.ModelClass;
112
+ const primaryKeyField = ModelClass.primaryKeyField;
113
+ const store = querysetStoreRegistry.getStore(queryset);
114
+ const querysetPks = store.render();
115
+ const opId = operationId || `${uuid7()}`;
116
+ const instances = querysetPks.map((pk) => ({ [primaryKeyField]: pk }));
117
+ return new Operation({
118
+ operationId: opId,
119
+ type: Type.DELETE,
120
+ instances: instances,
121
+ queryset: queryset,
122
+ args: {},
123
+ localOnly: queryset._optimisticOnly || false,
124
+ });
125
+ }
126
+ /**
127
+ * Create an UPDATE_INSTANCE operation
128
+ * @param {QuerySet} queryset - The queryset context
129
+ * @param {Object} data - The update data
130
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
131
+ * @returns {Operation} The created operation
132
+ */
133
+ static createUpdateInstanceOperation(queryset, data = {}, operationId = null) {
134
+ const ModelClass = queryset.ModelClass;
135
+ const primaryKeyField = ModelClass.primaryKeyField;
136
+ const store = querysetStoreRegistry.getStore(queryset);
137
+ const querysetPks = store.render();
138
+ const opId = operationId || `${uuid7()}`;
139
+ const instances = querysetPks.map(pk => ({ ...data, [primaryKeyField]: pk }));
140
+ return new Operation({
141
+ operationId: opId,
142
+ type: Type.UPDATE_INSTANCE,
143
+ instances: instances,
144
+ queryset: queryset,
145
+ args: { data },
146
+ localOnly: queryset._optimisticOnly || false,
147
+ });
148
+ }
149
+ /**
150
+ * Create a DELETE_INSTANCE operation
151
+ * @param {QuerySet} queryset - The queryset context
152
+ * @param {Object} instanceData - The instance to delete (object with PK)
153
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
154
+ * @returns {Operation} The created operation
155
+ */
156
+ static createDeleteInstanceOperation(queryset, instanceData, operationId = null) {
157
+ const opId = operationId || `${uuid7()}`;
158
+ return new Operation({
159
+ operationId: opId,
160
+ type: Type.DELETE_INSTANCE,
161
+ instances: [instanceData],
162
+ queryset: queryset,
163
+ args: instanceData,
164
+ localOnly: queryset._optimisticOnly || false,
165
+ });
166
+ }
167
+ /**
168
+ * Create a GET_OR_CREATE operation with local filtering logic
169
+ * @param {QuerySet} queryset - The queryset context
170
+ * @param {Object} lookup - The lookup criteria
171
+ * @param {Object} defaults - The default values for creation
172
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
173
+ * @returns {Operation} The created operation
174
+ */
175
+ static createGetOrCreateOperation(queryset, lookup = {}, defaults = {}, operationId = null) {
176
+ const ModelClass = queryset.ModelClass;
177
+ const primaryKeyField = ModelClass.primaryKeyField;
178
+ const opId = operationId || `${uuid7()}`;
179
+ // Get all current instances from the store for local filtering
180
+ const modelStore = modelStoreRegistry.getStore(ModelClass);
181
+ const allInstances = modelStore.render();
182
+ // Create a queryset filter for the lookup criteria
183
+ const lookupFilter = { ...lookup };
184
+ const lookupQuerySet = new queryset.constructor(ModelClass).filter(lookupFilter);
185
+ const lookupQuery = lookupQuerySet.build();
186
+ // Use local filtering to find matching instances
187
+ const requiredPaths = getRequiredFields(lookupQuery, ModelClass);
188
+ const prunedData = allInstances.map(inst => pickRequiredFields(requiredPaths, ModelClass.fromPk(inst[primaryKeyField], queryset)));
189
+ const matchingPks = processQuery(prunedData, lookupQuery, ModelClass);
190
+ // Find the corresponding instances
191
+ const matchingInstances = allInstances.filter(inst => matchingPks.includes(inst[primaryKeyField]));
192
+ const isCreatingNew = matchingInstances.length === 0;
193
+ const effectiveType = isCreatingNew ? Type.CREATE : Type.UPDATE;
194
+ // Create the instance data
195
+ const instanceData = isCreatingNew
196
+ ? { ...lookup, ...defaults, [primaryKeyField]: opId }
197
+ : matchingInstances[0];
198
+ return new Operation({
199
+ operationId: opId,
200
+ type: effectiveType,
201
+ instances: [instanceData],
202
+ queryset: queryset,
203
+ args: { lookup, defaults },
204
+ localOnly: queryset._optimisticOnly || false,
205
+ });
206
+ }
207
+ /**
208
+ * Create an UPDATE_OR_CREATE operation with local filtering logic
209
+ * @param {QuerySet} queryset - The queryset context
210
+ * @param {Object} lookup - The lookup criteria
211
+ * @param {Object} defaults - The default values for creation/update
212
+ * @param {string} [operationId] - Optional operation ID (for hotpath events)
213
+ * @returns {Operation} The created operation
214
+ */
215
+ static createUpdateOrCreateOperation(queryset, lookup = {}, defaults = {}, operationId = null) {
216
+ const ModelClass = queryset.ModelClass;
217
+ const primaryKeyField = ModelClass.primaryKeyField;
218
+ const opId = operationId || `${uuid7()}`;
219
+ // Get all current instances from the store for local filtering
220
+ const modelStore = modelStoreRegistry.getStore(ModelClass);
221
+ const allInstances = modelStore.render();
222
+ // Create a queryset filter for the lookup criteria
223
+ const lookupFilter = { ...lookup };
224
+ const lookupQuerySet = new queryset.constructor(ModelClass).filter(lookupFilter);
225
+ const lookupQuery = lookupQuerySet.build();
226
+ // Use local filtering to find matching instances
227
+ const requiredPaths = getRequiredFields(lookupQuery, ModelClass);
228
+ const prunedData = allInstances.map(inst => pickRequiredFields(requiredPaths, ModelClass.fromPk(inst[primaryKeyField], queryset)));
229
+ const matchingPks = processQuery(prunedData, lookupQuery, ModelClass);
230
+ // Find the corresponding instances
231
+ const matchingInstances = allInstances.filter(inst => matchingPks.includes(inst[primaryKeyField]));
232
+ const isCreatingNew = matchingInstances.length === 0;
233
+ const isUpdating = !isCreatingNew;
234
+ const effectiveType = isCreatingNew ? Type.CREATE : Type.UPDATE;
235
+ // Create the instance data
236
+ const instanceData = isCreatingNew
237
+ ? { ...lookup, ...defaults, [primaryKeyField]: opId }
238
+ : { ...matchingInstances[0], ...defaults };
239
+ return new Operation({
240
+ operationId: opId,
241
+ type: effectiveType,
242
+ instances: [instanceData],
243
+ queryset: queryset,
244
+ args: { lookup, defaults },
245
+ localOnly: queryset._optimisticOnly || false,
246
+ });
247
+ }
248
+ }