model.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import { reactive, ref } from "vue";
  2. import { defineStore } from "pinia";
  3. import { generateUuid } from "@common/utils/generateUuid";
  4. import { useWebsocketStore } from "./websocket";
  5. import Model from "@/Model";
  6. export const useModelStore = defineStore("model", () => {
  7. const { runJob, subscribe, unsubscribe } = useWebsocketStore();
  8. const models = ref([]);
  9. const permissions = ref({});
  10. const createdSubcription = ref(null);
  11. const subscriptions = ref({
  12. created: {},
  13. updated: {},
  14. deleted: {}
  15. });
  16. const getUserModelPermissions = async (modelName: string) => {
  17. if (permissions.value[modelName]) return permissions.value[modelName];
  18. const data = await runJob("data.users.getModelPermissions", {
  19. modelName
  20. });
  21. permissions.value[modelName] = data;
  22. return permissions.value[modelName];
  23. };
  24. const hasPermission = async (modelName: string, permission: string) => {
  25. const data = await getUserModelPermissions(modelName);
  26. return !!data[permission];
  27. };
  28. const unregisterModels = async modelIds =>
  29. Promise.all(
  30. (Array.isArray(modelIds) ? modelIds : [modelIds]).map(
  31. async modelId => {
  32. const model = models.value.find(
  33. model => model._id === modelId
  34. );
  35. if (!model || model.getUses() > 1) {
  36. model?.removeUse();
  37. return;
  38. }
  39. await model.unregisterRelations();
  40. const { updated, deleted } = model.getSubscriptions() ?? {};
  41. if (updated)
  42. await unsubscribe(
  43. `model.${model.getName()}.updated.${modelId}`,
  44. updated
  45. );
  46. if (deleted)
  47. await unsubscribe(
  48. `model.${model.getName()}.deleted.${modelId}`,
  49. deleted
  50. );
  51. models.value.splice(
  52. models.value.findIndex(model => model._id === modelId),
  53. 1
  54. );
  55. }
  56. )
  57. );
  58. const onCreatedCallback = async (modelName: string, data) => {
  59. if (!subscriptions.value.created[modelName]) return;
  60. await Promise.all(
  61. Object.values(subscriptions.value.created[modelName]).map(
  62. async subscription => subscription(data) // TODO: Error handling
  63. )
  64. );
  65. };
  66. const onCreated = async (
  67. modelName: string,
  68. callback: (data?: any) => any
  69. ) => {
  70. if (!createdSubcription.value)
  71. createdSubcription.value = await subscribe(
  72. `model.${modelName}.created`,
  73. data => onCreatedCallback(modelName, data)
  74. );
  75. const uuid = generateUuid();
  76. subscriptions.value.created[modelName] ??= {};
  77. subscriptions.value.created[modelName][uuid] = callback;
  78. return uuid;
  79. };
  80. const onUpdated = async (
  81. modelName: string,
  82. callback: (data?: any) => any
  83. ) => {
  84. const uuid = generateUuid();
  85. subscriptions.value.updated[modelName] ??= {};
  86. subscriptions.value.updated[modelName][uuid] = callback;
  87. return uuid;
  88. };
  89. const onUpdatedCallback = async (modelName: string, { doc }) => {
  90. const model = models.value.find(model => model._id === doc._id);
  91. if (model) model.updateData(doc);
  92. if (!subscriptions.value.updated[modelName]) return;
  93. await Promise.all(
  94. Object.values(subscriptions.value.updated[modelName]).map(
  95. async subscription => subscription(data) // TODO: Error handling
  96. )
  97. );
  98. };
  99. const onDeleted = async (
  100. modelName: string,
  101. callback: (data?: any) => any
  102. ) => {
  103. const uuid = generateUuid();
  104. subscriptions.value.deleted[modelName] ??= {};
  105. subscriptions.value.deleted[modelName][uuid] = callback;
  106. return uuid;
  107. };
  108. const onDeletedCallback = async (modelName: string, data) => {
  109. const { oldDoc } = data;
  110. if (subscriptions.value.deleted[modelName])
  111. await Promise.all(
  112. Object.values(subscriptions.value.deleted[modelName]).map(
  113. async subscription => subscription(data) // TODO: Error handling
  114. )
  115. );
  116. const index = models.value.findIndex(model => model._id === oldDoc._id);
  117. if (index > -1) await unregisterModels(oldDoc._id);
  118. };
  119. const removeCallback = async (
  120. modelName: string,
  121. type: "created" | "updated" | "deleted",
  122. uuid: string
  123. ) => {
  124. if (
  125. !subscriptions.value[type][modelName] ||
  126. !subscriptions.value[type][modelName][uuid]
  127. )
  128. return;
  129. delete subscriptions.value[type][modelName][uuid];
  130. if (
  131. type === "created" &&
  132. Object.keys(subscriptions.value.created[modelName]).length === 0
  133. ) {
  134. await unsubscribe(
  135. `model.${modelName}.created`,
  136. createdSubcription.value
  137. );
  138. createdSubcription.value = null;
  139. }
  140. };
  141. const registerModels = async (
  142. docs,
  143. relations?: Record<string, string | string[]>
  144. ) =>
  145. Promise.all(
  146. (Array.isArray(docs) ? docs : [docs]).map(async _doc => {
  147. const existingRef = models.value.find(
  148. model => model._id === _doc._id
  149. );
  150. const docRef = existingRef ?? reactive(new Model(_doc));
  151. docRef.addUse();
  152. if (existingRef) return docRef;
  153. if (relations && relations[docRef._name])
  154. await docRef.loadRelations(relations[docRef._name]);
  155. models.value.push(docRef);
  156. const updatedUuid = await subscribe(
  157. `model.${docRef._name}.updated.${_doc._id}`,
  158. data => onUpdatedCallback(docRef._name, data)
  159. );
  160. const deletedUuid = await subscribe(
  161. `model.${docRef._name}.deleted.${_doc._id}`,
  162. data => onDeletedCallback(docRef._name, data)
  163. );
  164. docRef.setSubscriptions(updatedUuid, deletedUuid);
  165. return docRef;
  166. })
  167. );
  168. const findById = async (modelName: string, _id) => {
  169. const existingModel = models.value.find(model => model._id === _id);
  170. if (existingModel) return existingModel;
  171. return runJob(`data.${modelName}.findById`, { _id });
  172. };
  173. const loadModels = async (
  174. modelName: string,
  175. modelIds: string | string[],
  176. relations?: Record<string, string | string[]>
  177. ) =>
  178. Promise.all(
  179. (Array.isArray(modelIds) ? modelIds : [modelIds]).map(
  180. async modelId => {
  181. let model = models.value.find(
  182. model =>
  183. model._id === modelId && model._name === modelName
  184. );
  185. model ??= await findById(modelName, modelId);
  186. const [ref] = await registerModels(model, relations);
  187. return ref;
  188. }
  189. )
  190. );
  191. return {
  192. models,
  193. permissions,
  194. subscriptions,
  195. onCreated,
  196. onUpdated,
  197. onDeleted,
  198. removeCallback,
  199. registerModels,
  200. unregisterModels,
  201. getUserModelPermissions,
  202. hasPermission,
  203. findById,
  204. loadModels
  205. };
  206. });