@warp-drive/core 5.8.0-beta.0 → 5.8.0-beta.1

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 (275) hide show
  1. package/README.md +23 -36
  2. package/declarations/build-config.d.ts +18 -1
  3. package/declarations/configure.d.ts +1 -1
  4. package/declarations/graph/-private/-edge-definition.d.ts +12 -2
  5. package/declarations/index.d.ts +90 -8
  6. package/declarations/reactive/-private/default-mode.d.ts +1 -1
  7. package/declarations/reactive/-private/document.d.ts +58 -46
  8. package/declarations/reactive/-private/fields/extension.d.ts +1 -1
  9. package/declarations/reactive/-private/fields/managed-array.d.ts +2 -2
  10. package/declarations/reactive/-private/fields/managed-object.d.ts +1 -1
  11. package/declarations/reactive/-private/record.d.ts +10 -1
  12. package/declarations/reactive/-private/schema.d.ts +77 -4
  13. package/declarations/reactive/-private.d.ts +1 -0
  14. package/declarations/reactive.d.ts +13 -8
  15. package/declarations/request/-private/types.d.ts +1 -1
  16. package/declarations/request.d.ts +47 -0
  17. package/declarations/signals/-leaked.d.ts +2 -0
  18. package/declarations/signals/-private.d.ts +6 -0
  19. package/declarations/{store/-private/new-core-tmp → signals}/promise-state.d.ts +2 -1
  20. package/declarations/{store/-private/new-core-tmp → signals}/request-state.d.ts +6 -6
  21. package/declarations/{store/-private/new-core-tmp → signals}/request-subscription.d.ts +4 -4
  22. package/declarations/store/-private/cache-handler/types.d.ts +2 -16
  23. package/declarations/store/-private/caches/instance-cache.d.ts +5 -6
  24. package/declarations/store/-private/default-cache-policy.d.ts +147 -129
  25. package/declarations/store/-private/managers/cache-capabilities-manager.d.ts +1 -1
  26. package/declarations/store/-private/managers/cache-key-manager.d.ts +26 -8
  27. package/declarations/store/-private/managers/cache-manager.d.ts +7 -18
  28. package/declarations/store/-private/managers/notification-manager.d.ts +1 -1
  29. package/declarations/store/-private/record-arrays/legacy-many-array.d.ts +1 -1
  30. package/declarations/store/-private/record-arrays/resource-array.d.ts +1 -1
  31. package/declarations/store/-private/store-service.d.ts +43 -64
  32. package/declarations/store/-private.d.ts +0 -6
  33. package/declarations/store/-types/q/cache-capabilities-manager.d.ts +1 -1
  34. package/declarations/store/deprecated/-private.d.ts +2 -2
  35. package/declarations/store/deprecated/store.d.ts +33 -32
  36. package/declarations/store.d.ts +1 -0
  37. package/declarations/types/cache.d.ts +8 -6
  38. package/declarations/types/record.d.ts +132 -0
  39. package/declarations/types/request.d.ts +26 -14
  40. package/declarations/types/schema/fields.d.ts +37 -13
  41. package/declarations/{store/-types/q → types/schema}/schema-service.d.ts +15 -13
  42. package/declarations/types/spec/document.d.ts +34 -0
  43. package/declarations/types/symbols.d.ts +2 -2
  44. package/declarations/types.d.ts +1 -1
  45. package/dist/build-config.js +1 -1
  46. package/dist/configure-DPUFCemT.js +1940 -0
  47. package/dist/configure.js +2 -1
  48. package/dist/default-cache-policy-D7_u4YRH.js +572 -0
  49. package/dist/{context-C_7OLieY.js → future-BKkJJkj7.js} +174 -174
  50. package/dist/graph/-private.js +16 -6
  51. package/dist/{request-state-C955e0AL.js → index-CQP2NSqg.js} +8830 -9427
  52. package/dist/index.js +6 -382
  53. package/dist/reactive/-private.js +1 -1
  54. package/dist/reactive.js +4 -903
  55. package/dist/request.js +49 -1
  56. package/dist/signals/-leaked.js +1 -0
  57. package/dist/store/-private.js +1 -2
  58. package/dist/store.js +1 -533
  59. package/dist/symbols-3C1OkYtZ.js +39 -0
  60. package/dist/types/-private.js +1 -1
  61. package/dist/types/record.js +127 -0
  62. package/dist/types/request.js +14 -12
  63. package/dist/types/runtime.js +1 -1
  64. package/dist/types/schema/fields.js +14 -0
  65. package/dist/types/schema/schema-service.js +0 -0
  66. package/dist/types/symbols.js +2 -2
  67. package/dist/unpkg/dev/-leaked-Co0EI6Go.js +1939 -0
  68. package/dist/unpkg/dev/build-config/babel-macros.js +1 -0
  69. package/dist/unpkg/dev/build-config/canary-features.js +1 -0
  70. package/dist/unpkg/dev/build-config/debugging.js +1 -0
  71. package/dist/unpkg/dev/build-config/deprecations.js +1 -0
  72. package/dist/unpkg/dev/build-config/env.js +1 -0
  73. package/dist/unpkg/dev/build-config/macros.js +1 -0
  74. package/dist/unpkg/dev/build-config.js +1 -0
  75. package/dist/unpkg/dev/configure.js +1 -0
  76. package/dist/unpkg/dev/future-DFfOzSoe.js +672 -0
  77. package/dist/unpkg/dev/graph/-private.js +3132 -0
  78. package/dist/unpkg/dev/index-BzcBOnw9.js +9392 -0
  79. package/dist/unpkg/dev/index.js +6 -0
  80. package/dist/unpkg/dev/reactive/-private.js +1 -0
  81. package/dist/unpkg/dev/reactive.js +3 -0
  82. package/dist/unpkg/dev/request.js +49 -0
  83. package/dist/unpkg/dev/runtime-E1M51_-O.js +135 -0
  84. package/dist/unpkg/dev/signals/-leaked.js +1 -0
  85. package/dist/unpkg/dev/store/-private.js +55 -0
  86. package/dist/unpkg/dev/store.js +558 -0
  87. package/dist/unpkg/dev/types/-private.js +69 -0
  88. package/dist/unpkg/dev/types/cache/aliases.js +0 -0
  89. package/dist/unpkg/dev/types/cache/change.js +0 -0
  90. package/dist/unpkg/dev/types/cache/mutations.js +0 -0
  91. package/dist/unpkg/dev/types/cache/operations.js +0 -0
  92. package/dist/unpkg/dev/types/cache/relationship.js +0 -0
  93. package/dist/unpkg/dev/types/cache.js +0 -0
  94. package/dist/unpkg/dev/types/graph.js +0 -0
  95. package/dist/unpkg/dev/types/identifier.js +61 -0
  96. package/dist/unpkg/dev/types/json/raw.js +0 -0
  97. package/dist/unpkg/dev/types/params.js +0 -0
  98. package/dist/unpkg/dev/types/record.js +191 -0
  99. package/dist/unpkg/dev/types/request.js +77 -0
  100. package/dist/unpkg/dev/types/runtime.js +34 -0
  101. package/dist/unpkg/dev/types/schema/concepts.js +0 -0
  102. package/dist/unpkg/dev/types/schema/fields.js +505 -0
  103. package/dist/unpkg/dev/types/schema/fields.type-test.js +0 -0
  104. package/dist/unpkg/dev/types/schema/schema-service.js +0 -0
  105. package/dist/unpkg/dev/types/spec/document.js +0 -0
  106. package/dist/unpkg/dev/types/spec/error.js +0 -0
  107. package/dist/unpkg/dev/types/spec/json-api-raw.js +0 -0
  108. package/dist/unpkg/dev/types/symbols.js +84 -0
  109. package/dist/unpkg/dev/types/utils.js +0 -0
  110. package/dist/unpkg/dev/types.js +0 -0
  111. package/dist/unpkg/dev/utils/string.js +91 -0
  112. package/dist/unpkg/dev-deprecated/-leaked-DjMeRqdU.js +1939 -0
  113. package/dist/unpkg/dev-deprecated/-private-3C1OkYtZ.js +39 -0
  114. package/dist/unpkg/dev-deprecated/build-config/babel-macros.js +1 -0
  115. package/dist/unpkg/dev-deprecated/build-config/canary-features.js +1 -0
  116. package/dist/unpkg/dev-deprecated/build-config/debugging.js +1 -0
  117. package/dist/unpkg/dev-deprecated/build-config/deprecations.js +1 -0
  118. package/dist/unpkg/dev-deprecated/build-config/env.js +1 -0
  119. package/dist/unpkg/dev-deprecated/build-config/macros.js +1 -0
  120. package/dist/unpkg/dev-deprecated/build-config.js +1 -0
  121. package/dist/unpkg/dev-deprecated/configure.js +1 -0
  122. package/dist/unpkg/dev-deprecated/future-DFfOzSoe.js +672 -0
  123. package/dist/unpkg/dev-deprecated/graph/-private.js +3327 -0
  124. package/dist/unpkg/dev-deprecated/index-6TnTv-WG.js +10007 -0
  125. package/dist/unpkg/dev-deprecated/index.js +5 -0
  126. package/dist/unpkg/dev-deprecated/reactive/-private.js +1 -0
  127. package/dist/unpkg/dev-deprecated/reactive.js +3 -0
  128. package/dist/unpkg/dev-deprecated/request.js +49 -0
  129. package/dist/unpkg/dev-deprecated/runtime-DAu5b0IR.js +135 -0
  130. package/dist/unpkg/dev-deprecated/signals/-leaked.js +1 -0
  131. package/dist/unpkg/dev-deprecated/store/-private.js +1 -0
  132. package/dist/unpkg/dev-deprecated/store.js +558 -0
  133. package/dist/unpkg/dev-deprecated/types/-private.js +69 -0
  134. package/dist/unpkg/dev-deprecated/types/cache/aliases.js +0 -0
  135. package/dist/unpkg/dev-deprecated/types/cache/change.js +0 -0
  136. package/dist/unpkg/dev-deprecated/types/cache/mutations.js +0 -0
  137. package/dist/unpkg/dev-deprecated/types/cache/operations.js +0 -0
  138. package/dist/unpkg/dev-deprecated/types/cache/relationship.js +0 -0
  139. package/dist/unpkg/dev-deprecated/types/cache.js +0 -0
  140. package/dist/unpkg/dev-deprecated/types/graph.js +0 -0
  141. package/dist/unpkg/dev-deprecated/types/identifier.js +61 -0
  142. package/dist/unpkg/dev-deprecated/types/json/raw.js +0 -0
  143. package/dist/unpkg/dev-deprecated/types/params.js +0 -0
  144. package/dist/unpkg/dev-deprecated/types/record.js +191 -0
  145. package/dist/unpkg/dev-deprecated/types/request.js +77 -0
  146. package/dist/unpkg/dev-deprecated/types/runtime.js +34 -0
  147. package/dist/unpkg/dev-deprecated/types/schema/concepts.js +0 -0
  148. package/dist/unpkg/dev-deprecated/types/schema/fields.js +505 -0
  149. package/dist/unpkg/dev-deprecated/types/schema/fields.type-test.js +0 -0
  150. package/dist/unpkg/dev-deprecated/types/schema/schema-service.js +0 -0
  151. package/dist/unpkg/dev-deprecated/types/spec/document.js +0 -0
  152. package/dist/unpkg/dev-deprecated/types/spec/error.js +0 -0
  153. package/dist/unpkg/dev-deprecated/types/spec/json-api-raw.js +0 -0
  154. package/dist/unpkg/dev-deprecated/types/symbols.js +84 -0
  155. package/dist/unpkg/dev-deprecated/types/utils.js +0 -0
  156. package/dist/unpkg/dev-deprecated/types.js +0 -0
  157. package/dist/unpkg/dev-deprecated/utils/string.js +91 -0
  158. package/dist/unpkg/prod/-leaked-DUONXQDB.js +1676 -0
  159. package/dist/unpkg/prod/-private-sql1_mdx.js +39 -0
  160. package/dist/unpkg/prod/build-config/babel-macros.js +1 -0
  161. package/dist/unpkg/prod/build-config/canary-features.js +1 -0
  162. package/dist/unpkg/prod/build-config/debugging.js +1 -0
  163. package/dist/unpkg/prod/build-config/deprecations.js +1 -0
  164. package/dist/unpkg/prod/build-config/env.js +1 -0
  165. package/dist/unpkg/prod/build-config/macros.js +1 -0
  166. package/dist/unpkg/prod/build-config.js +1 -0
  167. package/dist/unpkg/prod/configure.js +2 -0
  168. package/dist/unpkg/prod/graph/-private.js +2235 -0
  169. package/dist/unpkg/prod/handler-EU_8ncB2.js +1619 -0
  170. package/dist/unpkg/prod/index.js +483 -0
  171. package/dist/unpkg/prod/promise-cache-DIT8Ypjq.js +19 -0
  172. package/dist/unpkg/prod/reactive/-private.js +1 -0
  173. package/dist/unpkg/prod/reactive.js +30 -0
  174. package/dist/unpkg/prod/request-BrJSCG6r.js +421 -0
  175. package/dist/unpkg/prod/request.js +2 -0
  176. package/dist/unpkg/prod/schema-BSkHyoWz.js +5219 -0
  177. package/dist/unpkg/prod/signals/-leaked.js +1 -0
  178. package/dist/unpkg/prod/store/-private.js +126 -0
  179. package/dist/unpkg/prod/store.js +437 -0
  180. package/dist/unpkg/prod/types/-private.js +49 -0
  181. package/dist/unpkg/prod/types/cache/aliases.js +0 -0
  182. package/dist/unpkg/prod/types/cache/change.js +0 -0
  183. package/dist/unpkg/prod/types/cache/mutations.js +0 -0
  184. package/dist/unpkg/prod/types/cache/operations.js +0 -0
  185. package/dist/unpkg/prod/types/cache/relationship.js +0 -0
  186. package/dist/unpkg/prod/types/cache.js +0 -0
  187. package/dist/unpkg/prod/types/graph.js +0 -0
  188. package/dist/unpkg/prod/types/identifier.js +61 -0
  189. package/dist/unpkg/prod/types/json/raw.js +0 -0
  190. package/dist/unpkg/prod/types/params.js +0 -0
  191. package/dist/unpkg/prod/types/record.js +191 -0
  192. package/dist/unpkg/prod/types/request.js +77 -0
  193. package/dist/unpkg/prod/types/runtime.js +34 -0
  194. package/dist/unpkg/prod/types/schema/concepts.js +0 -0
  195. package/dist/unpkg/prod/types/schema/fields.js +505 -0
  196. package/dist/unpkg/prod/types/schema/fields.type-test.js +0 -0
  197. package/dist/unpkg/prod/types/schema/schema-service.js +0 -0
  198. package/dist/unpkg/prod/types/spec/document.js +0 -0
  199. package/dist/unpkg/prod/types/spec/error.js +0 -0
  200. package/dist/unpkg/prod/types/spec/json-api-raw.js +0 -0
  201. package/dist/unpkg/prod/types/symbols.js +84 -0
  202. package/dist/unpkg/prod/types/utils.js +0 -0
  203. package/dist/unpkg/prod/types.js +0 -0
  204. package/dist/unpkg/prod/utils/string.js +72 -0
  205. package/dist/unpkg/prod-deprecated/-leaked-DRNv9VIX.js +1676 -0
  206. package/dist/unpkg/prod-deprecated/-private-3C1OkYtZ.js +39 -0
  207. package/dist/unpkg/prod-deprecated/build-config/babel-macros.js +1 -0
  208. package/dist/unpkg/prod-deprecated/build-config/canary-features.js +1 -0
  209. package/dist/unpkg/prod-deprecated/build-config/debugging.js +1 -0
  210. package/dist/unpkg/prod-deprecated/build-config/deprecations.js +1 -0
  211. package/dist/unpkg/prod-deprecated/build-config/env.js +1 -0
  212. package/dist/unpkg/prod-deprecated/build-config/macros.js +1 -0
  213. package/dist/unpkg/prod-deprecated/build-config.js +1 -0
  214. package/dist/unpkg/prod-deprecated/configure.js +2 -0
  215. package/dist/unpkg/prod-deprecated/graph/-private.js +2408 -0
  216. package/dist/unpkg/prod-deprecated/handler-CCIu4sQ3.js +334 -0
  217. package/dist/unpkg/prod-deprecated/hooks-Dv4Np0MY.js +26 -0
  218. package/dist/unpkg/prod-deprecated/index.js +483 -0
  219. package/dist/unpkg/prod-deprecated/promise-cache-DIT8Ypjq.js +19 -0
  220. package/dist/unpkg/prod-deprecated/reactive/-private.js +1 -0
  221. package/dist/unpkg/prod-deprecated/reactive.js +5 -0
  222. package/dist/unpkg/prod-deprecated/request-BrJSCG6r.js +421 -0
  223. package/dist/unpkg/prod-deprecated/request.js +2 -0
  224. package/dist/unpkg/prod-deprecated/schema-CJcjHv0E.js +6939 -0
  225. package/dist/unpkg/prod-deprecated/signals/-leaked.js +1 -0
  226. package/dist/unpkg/prod-deprecated/store/-private.js +88 -0
  227. package/dist/unpkg/prod-deprecated/store.js +437 -0
  228. package/dist/unpkg/prod-deprecated/types/-private.js +49 -0
  229. package/dist/unpkg/prod-deprecated/types/cache/aliases.js +0 -0
  230. package/dist/unpkg/prod-deprecated/types/cache/change.js +0 -0
  231. package/dist/unpkg/prod-deprecated/types/cache/mutations.js +0 -0
  232. package/dist/unpkg/prod-deprecated/types/cache/operations.js +0 -0
  233. package/dist/unpkg/prod-deprecated/types/cache/relationship.js +0 -0
  234. package/dist/unpkg/prod-deprecated/types/cache.js +0 -0
  235. package/dist/unpkg/prod-deprecated/types/graph.js +0 -0
  236. package/dist/unpkg/prod-deprecated/types/identifier.js +61 -0
  237. package/dist/unpkg/prod-deprecated/types/json/raw.js +0 -0
  238. package/dist/unpkg/prod-deprecated/types/params.js +0 -0
  239. package/dist/unpkg/prod-deprecated/types/record.js +191 -0
  240. package/dist/unpkg/prod-deprecated/types/request.js +77 -0
  241. package/dist/unpkg/prod-deprecated/types/runtime.js +34 -0
  242. package/dist/unpkg/prod-deprecated/types/schema/concepts.js +0 -0
  243. package/dist/unpkg/prod-deprecated/types/schema/fields.js +505 -0
  244. package/dist/unpkg/prod-deprecated/types/schema/fields.type-test.js +0 -0
  245. package/dist/unpkg/prod-deprecated/types/schema/schema-service.js +0 -0
  246. package/dist/unpkg/prod-deprecated/types/spec/document.js +0 -0
  247. package/dist/unpkg/prod-deprecated/types/spec/error.js +0 -0
  248. package/dist/unpkg/prod-deprecated/types/spec/json-api-raw.js +0 -0
  249. package/dist/unpkg/prod-deprecated/types/symbols.js +84 -0
  250. package/dist/unpkg/prod-deprecated/types/utils.js +0 -0
  251. package/dist/unpkg/prod-deprecated/types.js +0 -0
  252. package/dist/unpkg/prod-deprecated/utils/string.js +72 -0
  253. package/logos/README.md +2 -2
  254. package/logos/logo-yellow-slab.svg +1 -0
  255. package/logos/word-mark-black.svg +1 -0
  256. package/logos/word-mark-white.svg +1 -0
  257. package/package.json +12 -4
  258. package/declarations/store/-private/new-core-tmp/expensive-subscription.d.ts +0 -24
  259. package/dist/configure-C3x8YXzL.js +0 -181
  260. package/logos/NCC-1701-a-blue.svg +0 -4
  261. package/logos/NCC-1701-a-gold.svg +0 -4
  262. package/logos/NCC-1701-a-gold_100.svg +0 -1
  263. package/logos/NCC-1701-a-gold_base-64.txt +0 -1
  264. package/logos/NCC-1701-a.svg +0 -4
  265. package/logos/docs-badge.svg +0 -2
  266. package/logos/ember-data-logo-dark.svg +0 -12
  267. package/logos/ember-data-logo-light.svg +0 -12
  268. package/logos/social1.png +0 -0
  269. package/logos/social2.png +0 -0
  270. package/logos/warp-drive-logo-dark.svg +0 -4
  271. package/logos/warp-drive-logo-gold.svg +0 -4
  272. /package/declarations/{store/-private/new-core-tmp → signals}/reactivity/configure.d.ts +0 -0
  273. /package/declarations/{store/-private/new-core-tmp → signals}/reactivity/internal.d.ts +0 -0
  274. /package/declarations/{store/-private/new-core-tmp → signals}/reactivity/signal.d.ts +0 -0
  275. /package/dist/{symbols-sql1_mdx.js → unpkg/dev/-private-sql1_mdx.js} +0 -0
@@ -0,0 +1,2408 @@
1
+ import { getOrSetGlobal, peekTransient, setTransient } from '../types/-private.js';
2
+ function getStore(wrapper) {
3
+ return wrapper._store;
4
+ }
5
+ function expandingGet(cache, key1, key2) {
6
+ const mainCache = cache[key1] = cache[key1] || Object.create(null);
7
+ return mainCache[key2];
8
+ }
9
+ function expandingSet(cache, key1, key2, value) {
10
+ const mainCache = cache[key1] = cache[key1] || Object.create(null);
11
+ mainCache[key2] = value;
12
+ }
13
+ function checkIfNew(store, resourceKey) {
14
+ if (!resourceKey.id) {
15
+ return true;
16
+ }
17
+ return store.cache.isNew(resourceKey);
18
+ }
19
+ function isBelongsTo(relationship) {
20
+ return relationship.definition.kind === 'belongsTo';
21
+ }
22
+ function isImplicit(relationship) {
23
+ return relationship.definition.isImplicit;
24
+ }
25
+ function isHasMany(relationship) {
26
+ return relationship.definition.kind === 'hasMany';
27
+ }
28
+ function forAllRelatedIdentifiers(rel, cb) {
29
+ if (isBelongsTo(rel)) {
30
+ if (rel.remoteState) {
31
+ cb(rel.remoteState);
32
+ }
33
+ if (rel.localState && rel.localState !== rel.remoteState) {
34
+ cb(rel.localState);
35
+ }
36
+ } else if (isHasMany(rel)) {
37
+ // TODO
38
+ // rel.remoteMembers.forEach(cb);
39
+ // might be simpler if performance is not a concern
40
+ for (let i = 0; i < rel.remoteState.length; i++) {
41
+ const inverseIdentifier = rel.remoteState[i];
42
+ cb(inverseIdentifier);
43
+ }
44
+ rel.additions?.forEach(cb);
45
+ } else {
46
+ rel.localMembers.forEach(cb);
47
+ rel.remoteMembers.forEach(inverseIdentifier => {
48
+ if (!rel.localMembers.has(inverseIdentifier)) {
49
+ cb(inverseIdentifier);
50
+ }
51
+ });
52
+ }
53
+ }
54
+
55
+ /*
56
+ Removes the given identifier from BOTH remote AND local state.
57
+
58
+ This method is useful when either a deletion or a rollback on a new record
59
+ needs to entirely purge itself from an inverse relationship.
60
+ */
61
+ function removeIdentifierCompletelyFromRelationship(graph, relationship, value, silenceNotifications) {
62
+ if (isBelongsTo(relationship)) {
63
+ if (relationship.remoteState === value) {
64
+ relationship.remoteState = null;
65
+ }
66
+ if (relationship.localState === value) {
67
+ relationship.localState = null;
68
+ // This allows dematerialized inverses to be rematerialized
69
+ // we shouldn't be notifying here though, figure out where
70
+ // a notification was missed elsewhere.
71
+ {
72
+ notifyChange(graph, relationship);
73
+ }
74
+ }
75
+ } else if (isHasMany(relationship)) {
76
+ relationship.remoteMembers.delete(value);
77
+ relationship.additions?.delete(value);
78
+ const wasInRemovals = relationship.removals?.delete(value);
79
+ const canonicalIndex = relationship.remoteState.indexOf(value);
80
+ if (canonicalIndex !== -1) {
81
+ relationship.remoteState.splice(canonicalIndex, 1);
82
+ }
83
+ if (!wasInRemovals) {
84
+ const currentIndex = relationship.localState?.indexOf(value);
85
+ if (currentIndex !== -1 && currentIndex !== undefined) {
86
+ relationship.localState.splice(currentIndex, 1);
87
+ // This allows dematerialized inverses to be rematerialized
88
+ // we shouldn't be notifying here though, figure out where
89
+ // a notification was missed elsewhere.
90
+ {
91
+ notifyChange(graph, relationship);
92
+ }
93
+ }
94
+ }
95
+ } else {
96
+ relationship.remoteMembers.delete(value);
97
+ relationship.localMembers.delete(value);
98
+ }
99
+ }
100
+ function notifyChange(graph, relationship) {
101
+ if (!relationship.accessed) {
102
+ return;
103
+ }
104
+ const resourceKey = relationship.identifier;
105
+ const key = relationship.definition.key;
106
+ if (resourceKey === graph._removing) {
107
+ return;
108
+ }
109
+ graph.store.notifyChange(resourceKey, 'relationships', key);
110
+ }
111
+ function isLegacyField(field) {
112
+ return field.kind === 'belongsTo' || field.kind === 'hasMany';
113
+ }
114
+ function temporaryConvertToLegacy(field) {
115
+ return {
116
+ kind: field.kind === 'resource' ? 'belongsTo' : 'hasMany',
117
+ name: field.name,
118
+ type: field.type,
119
+ options: Object.assign({}, {
120
+ async: false,
121
+ inverse: null,
122
+ resetOnRemoteUpdate: false
123
+ }, field.options)
124
+ };
125
+ }
126
+
127
+ /**
128
+ *
129
+ * Given RHS (Right Hand Side)
130
+ *
131
+ * ```ts
132
+ * class User extends Model {
133
+ * @hasMany('animal', { async: false, inverse: 'owner' }) pets;
134
+ * }
135
+ * ```
136
+ *
137
+ * Given LHS (Left Hand Side)
138
+ *
139
+ * ```ts
140
+ * class Animal extends Model {
141
+ * @belongsTo('user', { async: false, inverse: 'pets' }) owner;
142
+ * }
143
+ * ```
144
+ *
145
+ * The UpgradedMeta for the RHS would be:
146
+ *
147
+ * ```ts
148
+ * {
149
+ * kind: 'hasMany',
150
+ * key: 'pets',
151
+ * type: 'animal',
152
+ * isAsync: false,
153
+ * isImplicit: false,
154
+ * isCollection: true,
155
+ * isPolymorphic: false,
156
+ * inverseKind: 'belongsTo',
157
+ * inverseKey: 'owner',
158
+ * inverseType: 'user',
159
+ * inverseIsAsync: false,
160
+ * inverseIsImplicit: false,
161
+ * inverseIsCollection: false,
162
+ * inverseIsPolymorphic: false,
163
+ * }
164
+ * ```
165
+ *
166
+ * The UpgradeMeta for the LHS would be:
167
+ *
168
+ * ```ts
169
+ * {
170
+ * kind: 'belongsTo',
171
+ * key: 'owner',
172
+ * type: 'user',
173
+ * isAsync: false,
174
+ * isImplicit: false,
175
+ * isCollection: false,
176
+ * isPolymorphic: false,
177
+ * inverseKind: 'hasMany',
178
+ * inverseKey: 'pets',
179
+ * inverseType: 'animal',
180
+ * inverseIsAsync: false,
181
+ * inverseIsImplicit: false,
182
+ * inverseIsCollection: true,
183
+ * inverseIsPolymorphic: false,
184
+ * }
185
+ * ```
186
+ *
187
+ * @private
188
+ */
189
+
190
+ const BOOL_LATER = null;
191
+ const STR_LATER = '';
192
+ const IMPLICIT_KEY_RAND = Date.now();
193
+ function implicitKeyFor(type, key) {
194
+ return `implicit-${type}:${key}${IMPLICIT_KEY_RAND}`;
195
+ }
196
+ function syncMeta(definition, inverseDefinition) {
197
+ definition.inverseKind = inverseDefinition.kind;
198
+ definition.inverseKey = inverseDefinition.key;
199
+ definition.inverseName = inverseDefinition.name;
200
+ definition.inverseType = inverseDefinition.type;
201
+ definition.inverseIsAsync = inverseDefinition.isAsync;
202
+ definition.inverseIsCollection = inverseDefinition.isCollection;
203
+ definition.inverseIsPolymorphic = inverseDefinition.isPolymorphic;
204
+ definition.inverseIsImplicit = inverseDefinition.isImplicit;
205
+ definition.inverseIsLinksMode = inverseDefinition.isLinksMode;
206
+ const resetOnRemoteUpdate = definition.resetOnRemoteUpdate === false || inverseDefinition.resetOnRemoteUpdate === false ? false : true;
207
+ definition.resetOnRemoteUpdate = resetOnRemoteUpdate;
208
+ inverseDefinition.resetOnRemoteUpdate = resetOnRemoteUpdate;
209
+ }
210
+ function upgradeMeta(meta) {
211
+ if (!isLegacyField(meta)) {
212
+ meta = temporaryConvertToLegacy(meta);
213
+ }
214
+ const niceMeta = {};
215
+ const options = meta.options;
216
+ niceMeta.kind = meta.kind;
217
+ niceMeta.key = meta.sourceKey ?? meta.name;
218
+ niceMeta.name = meta.name;
219
+ niceMeta.type = meta.type;
220
+ niceMeta.isAsync = options.async;
221
+ niceMeta.isImplicit = false;
222
+ niceMeta.isCollection = meta.kind === 'hasMany';
223
+ niceMeta.isPolymorphic = options && !!options.polymorphic;
224
+ niceMeta.isLinksMode = options.linksMode ?? false;
225
+ niceMeta.inverseKey = options && options.inverse || STR_LATER;
226
+ niceMeta.inverseName = options && options.inverse || STR_LATER;
227
+ niceMeta.inverseType = STR_LATER;
228
+ niceMeta.inverseIsAsync = BOOL_LATER;
229
+ niceMeta.inverseIsImplicit = options && options.inverse === null || BOOL_LATER;
230
+ niceMeta.inverseIsCollection = BOOL_LATER;
231
+ niceMeta.inverseIsLinksMode = BOOL_LATER;
232
+
233
+ // prettier-ignore
234
+ niceMeta.resetOnRemoteUpdate = !isLegacyField(meta) ? false : meta.options?.linksMode ? false : meta.options?.resetOnRemoteUpdate === false ? false : true;
235
+ return niceMeta;
236
+ }
237
+ function isLHS(info, type, key) {
238
+ const isSelfReferential = info.isSelfReferential;
239
+ const isRelationship = key === info.lhs_relationshipName;
240
+ if (isRelationship === true) {
241
+ return isSelfReferential === true ||
242
+ // itself
243
+ type === info.lhs_baseModelName ||
244
+ // base or non-polymorphic
245
+ // if the other side is polymorphic then we need to scan our modelNames
246
+ info.rhs_isPolymorphic && info.lhs_modelNames.includes(type) // polymorphic
247
+ ;
248
+ }
249
+ return false;
250
+ }
251
+ function upgradeDefinition(graph, key, propertyName, isImplicit = false) {
252
+ const cache = graph._definitionCache;
253
+ const storeWrapper = graph.store;
254
+ const polymorphicLookup = graph._potentialPolymorphicTypes;
255
+ const {
256
+ type
257
+ } = key;
258
+ let cached = /*#__NOINLINE__*/expandingGet(cache, type, propertyName);
259
+
260
+ // CASE: We have a cached resolution (null if no relationship exists)
261
+ if (cached !== undefined) {
262
+ return cached;
263
+ }
264
+ const relationships = storeWrapper.schema.fields(key);
265
+ const relationshipsBySourceKey = storeWrapper.schema.cacheFields?.(key) ?? relationships;
266
+ const meta = relationshipsBySourceKey.get(propertyName);
267
+ if (!meta) {
268
+ // TODO potentially we should just be permissive here since this is an implicit relationship
269
+ // and not require the lookup table to be populated
270
+ if (polymorphicLookup[type]) {
271
+ const altTypes = Object.keys(polymorphicLookup[type]);
272
+ for (let i = 0; i < altTypes.length; i++) {
273
+ const _cached = expandingGet(cache, altTypes[i], propertyName);
274
+ if (_cached) {
275
+ /*#__NOINLINE__*/expandingSet(cache, type, propertyName, _cached);
276
+ _cached.rhs_modelNames.push(type);
277
+ return _cached;
278
+ }
279
+ }
280
+ }
281
+ cache[type][propertyName] = null;
282
+ return null;
283
+ }
284
+ const definition = /*#__NOINLINE__*/upgradeMeta(meta);
285
+ let inverseDefinition;
286
+ let inverseKey;
287
+ const inverseType = definition.type;
288
+
289
+ // CASE: Inverse is explicitly null
290
+ if (definition.inverseKey === null) {
291
+ inverseDefinition = null;
292
+ } else {
293
+ inverseKey = /*#__NOINLINE__*/inverseForRelationship(getStore(storeWrapper), key, propertyName);
294
+
295
+ // CASE: If we are polymorphic, and we declared an inverse that is non-null
296
+ // we must assume that the lack of inverseKey means that there is no
297
+ // concrete type as the baseType, so we must construct and artificial
298
+ // placeholder
299
+ if (!inverseKey && definition.isPolymorphic && definition.inverseKey) {
300
+ inverseDefinition = {
301
+ kind: 'belongsTo',
302
+ // this must be updated when we find the first belongsTo or hasMany definition that matches
303
+ key: definition.inverseKey,
304
+ name: definition.inverseName,
305
+ type: type,
306
+ isAsync: false,
307
+ // this must be updated when we find the first belongsTo or hasMany definition that matches
308
+ isImplicit: false,
309
+ isCollection: false,
310
+ // this must be updated when we find the first belongsTo or hasMany definition that matches
311
+ isPolymorphic: false
312
+ }; // the rest of the fields are populated by syncMeta
313
+
314
+ // CASE: Inverse resolves to null
315
+ } else if (!inverseKey) {
316
+ inverseDefinition = null;
317
+ } else {
318
+ // CASE: We have an explicit inverse or were able to resolve one
319
+ // for the inverse we use "name" for lookup not "sourceKey"
320
+ const inverseDefinitions = storeWrapper.schema.fields({
321
+ type: inverseType
322
+ });
323
+ const metaFromInverse = inverseDefinitions.get(inverseKey);
324
+ inverseDefinition = upgradeMeta(metaFromInverse);
325
+ }
326
+ }
327
+
328
+ // CASE: We have no inverse
329
+ if (!inverseDefinition) {
330
+ // polish off meta
331
+ inverseKey = /*#__NOINLINE__*/implicitKeyFor(type, propertyName);
332
+ inverseDefinition = {
333
+ kind: 'implicit',
334
+ key: inverseKey,
335
+ type: type,
336
+ isAsync: false,
337
+ isImplicit: true,
338
+ isCollection: true,
339
+ // with implicits any number of records could point at us
340
+ isPolymorphic: false
341
+ }; // the rest of the fields are populated by syncMeta
342
+
343
+ syncMeta(definition, inverseDefinition);
344
+ syncMeta(inverseDefinition, definition);
345
+ const info = {
346
+ lhs_key: `${type}:${propertyName}`,
347
+ lhs_modelNames: [type],
348
+ lhs_baseModelName: type,
349
+ lhs_relationshipName: propertyName,
350
+ lhs_definition: definition,
351
+ lhs_isPolymorphic: definition.isPolymorphic,
352
+ rhs_key: inverseDefinition.key,
353
+ rhs_modelNames: [inverseType],
354
+ rhs_baseModelName: inverseType,
355
+ rhs_relationshipName: inverseDefinition.key,
356
+ rhs_definition: inverseDefinition,
357
+ rhs_isPolymorphic: false,
358
+ hasInverse: false,
359
+ isSelfReferential: type === inverseType,
360
+ // this could be wrong if we are self-referential but also polymorphic
361
+ isReflexive: false // we can't be reflexive if we don't define an inverse
362
+ };
363
+ expandingSet(cache, inverseType, inverseKey, info);
364
+ expandingSet(cache, type, propertyName, info);
365
+ return info;
366
+ }
367
+
368
+ // CASE: We do have an inverse
369
+ const baseType = inverseDefinition.type;
370
+ cached = expandingGet(cache, baseType, propertyName) || expandingGet(cache, inverseType, inverseKey);
371
+ if (cached) {
372
+ const _isLHS = cached.lhs_baseModelName === baseType;
373
+ const modelNames = _isLHS ? cached.lhs_modelNames : cached.rhs_modelNames;
374
+ // make this lookup easier in the future by caching the key
375
+ modelNames.push(type);
376
+ expandingSet(cache, type, propertyName, cached);
377
+ return cached;
378
+ }
379
+
380
+ // this is our first time so polish off the metas
381
+ syncMeta(definition, inverseDefinition);
382
+ syncMeta(inverseDefinition, definition);
383
+ const lhs_modelNames = [type];
384
+ if (type !== baseType) {
385
+ lhs_modelNames.push(baseType);
386
+ }
387
+ const isSelfReferential = baseType === inverseType;
388
+ const info = {
389
+ lhs_key: `${baseType}:${propertyName}`,
390
+ lhs_modelNames,
391
+ lhs_baseModelName: baseType,
392
+ lhs_relationshipName: propertyName,
393
+ lhs_definition: definition,
394
+ lhs_isPolymorphic: definition.isPolymorphic,
395
+ rhs_key: `${inverseType}:${inverseKey}`,
396
+ rhs_modelNames: [inverseType],
397
+ rhs_baseModelName: inverseType,
398
+ rhs_relationshipName: inverseKey,
399
+ rhs_definition: inverseDefinition,
400
+ rhs_isPolymorphic: inverseDefinition.isPolymorphic,
401
+ hasInverse: true,
402
+ isSelfReferential,
403
+ isReflexive: isSelfReferential && propertyName === inverseKey
404
+ };
405
+
406
+ // Create entries for the baseModelName as well as modelName to speed up
407
+ // inverse lookups
408
+ expandingSet(cache, baseType, propertyName, info);
409
+ expandingSet(cache, type, propertyName, info);
410
+
411
+ // Greedily populate the inverse
412
+ expandingSet(cache, inverseType, inverseKey, info);
413
+ return info;
414
+ }
415
+ function inverseForRelationship(store, resourceKey, key) {
416
+ const fields = store.schema.fields(resourceKey);
417
+ const definition = fields.get(key);
418
+ if (!definition) {
419
+ return null;
420
+ }
421
+ return definition.options.inverse;
422
+ }
423
+
424
+ /*
425
+ case many:1
426
+ ========
427
+ In a bi-directional graph with Many:1 edges, adding a value
428
+ results in up-to 3 discrete value transitions, while removing
429
+ a value is only 2 transitions.
430
+
431
+ For adding C to A
432
+ If: A <<-> B, C <->> D is the initial state,
433
+ and: B <->> A <<-> C, D is the final state
434
+
435
+ then we would undergo the following transitions.
436
+
437
+ add C to A
438
+ remove C from D
439
+ add A to C
440
+
441
+ For removing B from A
442
+ If: A <<-> B, C <->> D is the initial state,
443
+ and: A, B, C <->> D is the final state
444
+
445
+ then we would undergo the following transitions.
446
+
447
+ remove B from A
448
+ remove A from B
449
+
450
+ case many:many
451
+ ===========
452
+ In a bi-directional graph with Many:Many edges, adding or
453
+ removing a value requires only 2 value transitions.
454
+
455
+ For Adding
456
+ If: A<<->>B, C<<->>D is the initial state (double arrows representing the many side)
457
+ And: D<<->>C<<->>A<<->>B is the final state
458
+
459
+ Then we would undergo two transitions.
460
+
461
+ add C to A.
462
+ add A to C
463
+
464
+ For Removing
465
+ If: A<<->>B, C<<->>D is the initial state (double arrows representing the many side)
466
+ And: A, B, C<<->>D is the final state
467
+
468
+ Then we would undergo two transitions.
469
+
470
+ remove B from A
471
+ remove A from B
472
+
473
+ case many:?
474
+ ========
475
+ In a uni-directional graph with Many:? edges (modeled in WarpDrive with `inverse:null`) with
476
+ artificial (implicit) inverses, replacing a value results in 2 discrete value transitions.
477
+ This is because a Many:? relationship is effectively Many:Many.
478
+ */
479
+ function replaceRelatedRecords(graph, op, isRemote) {
480
+ if (isRemote) {
481
+ replaceRelatedRecordsRemote(graph, op, isRemote);
482
+ } else {
483
+ replaceRelatedRecordsLocal(graph, op, isRemote);
484
+ }
485
+ }
486
+ function replaceRelatedRecordsLocal(graph, op, isRemote) {
487
+ const resourceKeys = op.value;
488
+ const relationship = graph.get(op.record, op.field);
489
+ relationship.state.hasReceivedData = true;
490
+ const {
491
+ additions,
492
+ removals
493
+ } = relationship;
494
+ const {
495
+ inverseKey,
496
+ type
497
+ } = relationship.definition;
498
+ const {
499
+ record
500
+ } = op;
501
+ const wasDirty = relationship.isDirty;
502
+ let localBecameDirty = false;
503
+ const onAdd = resourceKey => {
504
+ // Since we are diffing against the remote state, we check
505
+ // if our previous local state did not contain this ResourceKey
506
+ const removalsHas = removals?.has(resourceKey);
507
+ if (removalsHas || !additions?.has(resourceKey)) {
508
+ if (type !== resourceKey.type) {
509
+ graph.registerPolymorphicType(type, resourceKey.type);
510
+ }
511
+
512
+ // we've added a record locally that wasn't in the local state before
513
+ localBecameDirty = true;
514
+ addToInverse(graph, resourceKey, inverseKey, op.record, isRemote);
515
+ if (removalsHas) {
516
+ removals.delete(resourceKey);
517
+ }
518
+ }
519
+ };
520
+ const onRemove = resourceKey => {
521
+ // Since we are diffing against the remote state, we check
522
+ // if our previous local state had contained this ResourceKey
523
+ const additionsHas = additions?.has(resourceKey);
524
+ if (additionsHas || !removals?.has(resourceKey)) {
525
+ // we've removed a record locally that was in the local state before
526
+ localBecameDirty = true;
527
+ removeFromInverse(graph, resourceKey, inverseKey, record, isRemote);
528
+ if (additionsHas) {
529
+ additions.delete(resourceKey);
530
+ }
531
+ }
532
+ };
533
+ const diff = diffCollection(resourceKeys, relationship, onAdd, onRemove);
534
+
535
+ // any additions no longer in the local state
536
+ // also need to be removed from the inverse
537
+ if (additions && additions.size > 0) {
538
+ additions.forEach(resourceKey => {
539
+ if (!diff.add.has(resourceKey)) {
540
+ localBecameDirty = true;
541
+ onRemove(resourceKey);
542
+ }
543
+ });
544
+ }
545
+
546
+ // any removals no longer in the local state
547
+ // also need to be added back to the inverse
548
+ if (removals && removals.size > 0) {
549
+ removals.forEach(resourceKey => {
550
+ if (!diff.del.has(resourceKey)) {
551
+ localBecameDirty = true;
552
+ onAdd(resourceKey);
553
+ }
554
+ });
555
+ }
556
+ const becameDirty = diff.changed || localBecameDirty;
557
+ relationship.additions = diff.add;
558
+ relationship.removals = diff.del;
559
+ relationship.localState = diff.finalState;
560
+
561
+ // we only notify if the localState changed and were not already dirty before
562
+ // because if we were already dirty then we have already notified
563
+ if (becameDirty && !wasDirty) {
564
+ notifyChange(graph, relationship);
565
+ }
566
+ }
567
+ function replaceRelatedRecordsRemote(graph, op, isRemote) {
568
+ const resourceKeys = op.value;
569
+ const relationship = graph.get(op.record, op.field);
570
+ if (isRemote) {
571
+ graph._addToTransaction(relationship);
572
+ }
573
+ const wasDirty = relationship.isDirty;
574
+ // if this is our first time receiving data
575
+ // we need to mark the relationship as dirty
576
+ // so that non-materializing APIs like `hasManyReference.value()`
577
+ // will get notified and updated.
578
+ if (!relationship.state.hasReceivedData) {
579
+ relationship.isDirty = true;
580
+ }
581
+ relationship.state.hasReceivedData = true;
582
+
583
+ // cache existing state
584
+ const {
585
+ definition
586
+ } = relationship;
587
+ const {
588
+ type
589
+ } = relationship.definition;
590
+ const diff = diffCollection(resourceKeys, relationship, resourceKey => {
591
+ if (type !== resourceKey.type) {
592
+ graph.registerPolymorphicType(type, resourceKey.type);
593
+ }
594
+ // commit additions
595
+ // TODO build this into the diff?
596
+ // because we are not dirty if this was a committed local addition
597
+ if (relationship.additions?.has(resourceKey)) {
598
+ relationship.additions.delete(resourceKey);
599
+ }
600
+ addToInverse(graph, resourceKey, definition.inverseKey, op.record, isRemote);
601
+ }, resourceKey => {
602
+ // commit removals
603
+ // TODO build this into the diff?
604
+ // because we are not dirty if this was a committed local addition
605
+ if (relationship.removals?.has(resourceKey)) {
606
+ relationship.removals.delete(resourceKey);
607
+ }
608
+ removeFromInverse(graph, resourceKey, definition.inverseKey, op.record, isRemote);
609
+ });
610
+
611
+ // replace existing state
612
+ relationship.remoteMembers = diff.finalSet;
613
+ relationship.remoteState = diff.finalState;
614
+
615
+ // changed also indicates a change in order
616
+ if (diff.changed) {
617
+ relationship.isDirty = true;
618
+ }
619
+
620
+ // TODO unsure if we need this but it
621
+ // may allow us to more efficiently patch
622
+ // the associated ManyArray
623
+ relationship._diff = diff;
624
+ {
625
+ // only do this for legacy hasMany, not collection
626
+ // and provide a way to incrementally migrate
627
+ if (
628
+ // we do not guard by diff.changed here
629
+ // because we want to clear local changes even if
630
+ // no change has occurred to preserve the legacy behavior
631
+ relationship.definition.kind === 'hasMany' && relationship.definition.resetOnRemoteUpdate !== false && (diff.changed || wasDirty)) {
632
+ if (relationship.removals) {
633
+ relationship.isDirty = true;
634
+ relationship.removals.forEach(resourceKey => {
635
+ // reverse the removal
636
+ // if we are still in removals at this point then
637
+ // we were not "committed" which means we are present
638
+ // in the remoteMembers. So we "add back" on the inverse.
639
+ addToInverse(graph, resourceKey, definition.inverseKey, op.record, false);
640
+ });
641
+ relationship.removals = null;
642
+ }
643
+ if (relationship.additions) {
644
+ relationship.additions.forEach(resourceKey => {
645
+ // reverse the addition
646
+ // if we are still in additions at this point then
647
+ // we were not "committed" which means we are not present
648
+ // in the remoteMembers. So we "remove" from the inverse.
649
+ // however we only do this if we are not a "new" record.
650
+ if (!checkIfNew(graph._realStore, resourceKey)) {
651
+ relationship.isDirty = true;
652
+ relationship.additions.delete(resourceKey);
653
+ removeFromInverse(graph, resourceKey, definition.inverseKey, op.record, false);
654
+ }
655
+ });
656
+ if (relationship.additions.size === 0) {
657
+ relationship.additions = null;
658
+ }
659
+ }
660
+ }
661
+ }
662
+ if (relationship.isDirty && !wasDirty) {
663
+ flushCanonical(graph, relationship);
664
+ }
665
+ }
666
+ function addToInverse(graph, resourceKey, key, value, isRemote) {
667
+ const relationship = graph.get(resourceKey, key);
668
+ const {
669
+ type
670
+ } = relationship.definition;
671
+ if (type !== value.type) {
672
+ graph.registerPolymorphicType(type, value.type);
673
+ }
674
+ if (isBelongsTo(relationship)) {
675
+ relationship.state.hasReceivedData = true;
676
+ relationship.state.isEmpty = false;
677
+ if (isRemote) {
678
+ graph._addToTransaction(relationship);
679
+ if (relationship.remoteState !== null) {
680
+ removeFromInverse(graph, relationship.remoteState, relationship.definition.inverseKey, resourceKey, isRemote);
681
+ }
682
+ relationship.remoteState = value;
683
+ }
684
+ if (relationship.localState !== value) {
685
+ if (!isRemote && relationship.localState) {
686
+ removeFromInverse(graph, relationship.localState, relationship.definition.inverseKey, resourceKey, isRemote);
687
+ }
688
+ relationship.localState = value;
689
+ notifyChange(graph, relationship);
690
+ }
691
+ } else if (isHasMany(relationship)) {
692
+ if (isRemote) {
693
+ // TODO this needs to alert stuffs
694
+ // And patch state better
695
+ // This is almost definitely wrong
696
+ // WARNING WARNING WARNING
697
+
698
+ if (!relationship.remoteMembers.has(value)) {
699
+ graph._addToTransaction(relationship);
700
+ relationship.remoteState.push(value);
701
+ relationship.remoteMembers.add(value);
702
+ if (relationship.additions?.has(value)) {
703
+ relationship.additions.delete(value);
704
+ } else {
705
+ relationship.isDirty = true;
706
+ relationship.state.hasReceivedData = true;
707
+ flushCanonical(graph, relationship);
708
+ }
709
+ }
710
+ } else {
711
+ // if we are not dirty but have a null localState then we
712
+ // are mutating a relationship that has never been fetched
713
+ // so we initialize localState to an empty array
714
+ if (!relationship.isDirty && !relationship.localState) {
715
+ relationship.localState = [];
716
+ }
717
+ if (_add(graph, resourceKey, relationship, value, null, isRemote)) {
718
+ notifyChange(graph, relationship);
719
+ }
720
+ }
721
+ } else {
722
+ if (isRemote) {
723
+ if (!relationship.remoteMembers.has(value)) {
724
+ relationship.remoteMembers.add(value);
725
+ relationship.localMembers.add(value);
726
+ }
727
+ } else {
728
+ if (!relationship.localMembers.has(value)) {
729
+ relationship.localMembers.add(value);
730
+ }
731
+ }
732
+ }
733
+ }
734
+ function notifyInverseOfPotentialMaterialization(graph, resourceKey, key, value, isRemote) {
735
+ const relationship = graph.get(resourceKey, key);
736
+ if (isHasMany(relationship) && isRemote && relationship.remoteMembers.has(value)) {
737
+ notifyChange(graph, relationship);
738
+ }
739
+ }
740
+ function removeFromInverse(graph, resourceKey, key, value, isRemote) {
741
+ const relationship = graph.get(resourceKey, key);
742
+ if (isBelongsTo(relationship)) {
743
+ relationship.state.isEmpty = true;
744
+ if (isRemote) {
745
+ graph._addToTransaction(relationship);
746
+ relationship.remoteState = null;
747
+ }
748
+ if (relationship.localState === value) {
749
+ relationship.localState = null;
750
+ notifyChange(graph, relationship);
751
+ }
752
+ } else if (isHasMany(relationship)) {
753
+ if (isRemote) {
754
+ graph._addToTransaction(relationship);
755
+ if (_removeRemote(relationship, value)) {
756
+ notifyChange(graph, relationship);
757
+ }
758
+ } else {
759
+ if (_removeLocal(relationship, value)) {
760
+ notifyChange(graph, relationship);
761
+ }
762
+ }
763
+ } else {
764
+ if (isRemote) {
765
+ relationship.remoteMembers.delete(value);
766
+ relationship.localMembers.delete(value);
767
+ } else {
768
+ if (value && relationship.localMembers.has(value)) {
769
+ relationship.localMembers.delete(value);
770
+ }
771
+ }
772
+ }
773
+ }
774
+ function flushCanonical(graph, rel) {
775
+ if (rel.accessed) {
776
+ graph._scheduleLocalSync(rel);
777
+ }
778
+ }
779
+ function replaceRelatedRecord(graph, op, isRemote = false) {
780
+ const relationship = graph.get(op.record, op.field);
781
+ if (isRemote) {
782
+ graph._addToTransaction(relationship);
783
+ }
784
+ const {
785
+ definition,
786
+ state
787
+ } = relationship;
788
+ const prop = isRemote ? 'remoteState' : 'localState';
789
+ const existingState = relationship[prop];
790
+
791
+ /*
792
+ case 1:1
793
+ ========
794
+ In a bi-directional graph with 1:1 edges, replacing a value
795
+ results in up-to 4 discrete value transitions.
796
+ If: A <-> B, C <-> D is the initial state,
797
+ and: A <-> C, B, D is the final state
798
+ then we would undergo the following 4 transitions.
799
+ remove A from B
800
+ add C to A
801
+ remove C from D
802
+ add A to C
803
+ case 1:many
804
+ ===========
805
+ In a bi-directional graph with 1:Many edges, replacing a value
806
+ results in up-to 3 discrete value transitions.
807
+ If: A<->>B<<->D, C<<->D is the initial state (double arrows representing the many side)
808
+ And: A<->>C<<->D, B<<->D is the final state
809
+ Then we would undergo three transitions.
810
+ remove A from B
811
+ add C to A.
812
+ add A to C
813
+ case 1:?
814
+ ========
815
+ In a uni-directional graph with 1:? edges (modeled in WarpDrive with `inverse:null`) with
816
+ artificial (implicit) inverses, replacing a value results in up-to 3 discrete value transitions.
817
+ This is because a 1:? relationship is effectively 1:many.
818
+ If: A->B, C->B is the initial state
819
+ And: A->C, C->B is the final state
820
+ Then we would undergo three transitions.
821
+ Remove A from B
822
+ Add C to A
823
+ Add A to C
824
+ */
825
+
826
+ // nothing for us to do
827
+ if (op.value === existingState) {
828
+ // if we were empty before but now know we are empty this needs to be true
829
+ state.hasReceivedData = true;
830
+ // if this is a remote update we still sync
831
+ if (isRemote) {
832
+ const {
833
+ localState
834
+ } = relationship;
835
+ // don't sync if localState is a new record and our remoteState is null
836
+ if (localState && checkIfNew(graph._realStore, localState) && !existingState) {
837
+ return;
838
+ }
839
+ if (existingState && localState === existingState) {
840
+ notifyInverseOfPotentialMaterialization(graph, existingState, definition.inverseKey, op.record, isRemote);
841
+ } else {
842
+ // if localState does not match existingState then we know
843
+ // we have a local mutation that has not been persisted yet
844
+ if (localState !== op.value && relationship.definition.resetOnRemoteUpdate !== false) {
845
+ relationship.localState = existingState;
846
+ notifyChange(graph, relationship);
847
+ }
848
+ }
849
+ }
850
+ return;
851
+ }
852
+
853
+ // remove this value from the inverse if required
854
+ if (existingState) {
855
+ removeFromInverse(graph, existingState, definition.inverseKey, op.record, isRemote);
856
+ }
857
+
858
+ // update value to the new value
859
+ relationship[prop] = op.value;
860
+ state.hasReceivedData = true;
861
+ state.isEmpty = op.value === null;
862
+ state.isStale = false;
863
+ state.hasFailedLoadAttempt = false;
864
+ if (op.value) {
865
+ if (definition.type !== op.value.type) {
866
+ // assert(
867
+ // `The '<${definition.inverseType}>.${op.field}' relationship expects only '${definition.type}' records since it is not polymorphic. Received a Record of type '${op.value.type}'`,
868
+ // definition.isPolymorphic
869
+ // );
870
+
871
+ // TODO this should now handle the deprecation warning if isPolymorphic is not set
872
+ // but the record does turn out to be polymorphic
873
+ // this should still assert if the user is relying on legacy inheritance/mixins to
874
+ // provide polymorphic behavior and has not yet added the polymorphic flags
875
+
876
+ graph.registerPolymorphicType(definition.type, op.value.type);
877
+ }
878
+ addToInverse(graph, op.value, definition.inverseKey, op.record, isRemote);
879
+ }
880
+ if (isRemote) {
881
+ const {
882
+ localState,
883
+ remoteState
884
+ } = relationship;
885
+ if (localState && checkIfNew(graph._realStore, localState) && !remoteState) {
886
+ return;
887
+ }
888
+ // when localState does not match the new remoteState and
889
+ // localState === existingState then we had no local mutation
890
+ // and we can safely sync the new remoteState to local
891
+ if (localState !== remoteState && localState === existingState) {
892
+ relationship.localState = remoteState;
893
+ notifyChange(graph, relationship);
894
+ // But when localState does not match the new remoteState and
895
+ // and localState !== existingState then we know we have a local mutation
896
+ // that has not been persisted yet.
897
+ } else {
898
+ if (localState !== remoteState && localState !== existingState && relationship.definition.resetOnRemoteUpdate !== false) {
899
+ relationship.localState = remoteState;
900
+ notifyChange(graph, relationship);
901
+ }
902
+ }
903
+ } else {
904
+ notifyChange(graph, relationship);
905
+ }
906
+ }
907
+ function _deprecatedCompare(priorLocalState, newState, newMembers, prevState, prevSet, onAdd, onDel, remoteClearsLocal) {
908
+ const newLength = newState.length;
909
+ const prevLength = prevState.length;
910
+ const iterationLength = Math.max(newLength, prevLength);
911
+ let changed = newMembers.size !== prevSet.size;
912
+ let remoteOrderChanged = false;
913
+ const added = new Set();
914
+ const removed = new Set();
915
+ const duplicates = new Map();
916
+ const finalSet = new Set();
917
+ const finalState = [];
918
+ const priorLocalLength = priorLocalState?.length ?? 0;
919
+ for (let i = 0, j = 0; i < iterationLength; i++) {
920
+ let adv = false;
921
+ let member;
922
+
923
+ // accumulate anything added
924
+ if (i < newLength) {
925
+ member = newState[i];
926
+ if (!finalSet.has(member)) {
927
+ finalState[j] = member;
928
+ finalSet.add(member);
929
+ adv = true;
930
+ if (!prevSet.has(member)) {
931
+ // Avoid unnecessarily notifying a change that already exists locally
932
+ if (i < priorLocalLength) {
933
+ const priorLocalMember = priorLocalState[i];
934
+ if (priorLocalMember !== member) {
935
+ changed = true;
936
+ }
937
+ }
938
+ added.add(member);
939
+ onAdd(member);
940
+ }
941
+ } else {
942
+ let list = duplicates.get(member);
943
+ if (list === undefined) {
944
+ list = [];
945
+ duplicates.set(member, list);
946
+ }
947
+ list.push(i);
948
+ }
949
+ }
950
+
951
+ // accumulate anything removed
952
+ if (i < prevLength) {
953
+ const prevMember = prevState[i];
954
+
955
+ // detect reordering, adjusting index for duplicates
956
+ // j is always less than i and so if i < prevLength, j < prevLength
957
+ if (member !== prevState[j]) {
958
+ // the new remote order does not match the current remote order
959
+ // indicating a change in membership or reordering
960
+ remoteOrderChanged = true;
961
+ // however: if the new remote order matches the current local order
962
+ // we can disregard the change notification generation so long as
963
+ // we are not configured to reset on remote update (which is deprecated)
964
+ {
965
+ if (!remoteClearsLocal && i < priorLocalLength) {
966
+ const priorLocalMember = priorLocalState[j];
967
+ if (priorLocalMember !== member) {
968
+ changed = true;
969
+ }
970
+ } else {
971
+ changed = true;
972
+ }
973
+ }
974
+
975
+ // if remote order hasn't changed but local order differs
976
+ // and we are configured to reset on remote update (which is deprecated)
977
+ // then we still need to mark the relationship as changed
978
+ } else {
979
+ if (remoteClearsLocal) {
980
+ if (!changed && j < priorLocalLength) {
981
+ const priorLocalMember = priorLocalState[j];
982
+ if (priorLocalMember !== member) {
983
+ changed = true;
984
+ }
985
+ }
986
+ }
987
+ }
988
+ if (!newMembers.has(prevMember)) {
989
+ changed = true;
990
+ removed.add(prevMember);
991
+ onDel(prevMember);
992
+ }
993
+ } else if (adv && j < prevLength && member !== prevState[j]) {
994
+ changed = true;
995
+ }
996
+ if (adv) {
997
+ j++;
998
+ }
999
+ }
1000
+ const diff = {
1001
+ add: added,
1002
+ del: removed,
1003
+ finalState,
1004
+ finalSet,
1005
+ changed,
1006
+ remoteOrderChanged
1007
+ };
1008
+ return {
1009
+ diff,
1010
+ duplicates
1011
+ };
1012
+ }
1013
+ function _compare(priorLocalState, finalState, finalSet, prevState, prevSet, onAdd, onDel, remoteClearsLocal) {
1014
+ const finalLength = finalState.length;
1015
+ const prevLength = prevState.length;
1016
+ const iterationLength = Math.max(finalLength, prevLength);
1017
+ const equalLength = priorLocalState ? finalLength === priorLocalState.length : finalLength === prevLength;
1018
+ let remoteOrderChanged = finalSet.size !== prevSet.size;
1019
+ let changed = priorLocalState ? finalSet.size !== priorLocalState.length : remoteOrderChanged;
1020
+ const added = new Set();
1021
+ const removed = new Set();
1022
+ const priorLocalLength = priorLocalState?.length ?? 0;
1023
+ for (let i = 0; i < iterationLength; i++) {
1024
+ let member;
1025
+
1026
+ // accumulate anything added
1027
+ if (i < finalLength) {
1028
+ member = finalState[i];
1029
+ if (!prevSet.has(member)) {
1030
+ // Avoid unnecessarily notifying a change that already exists locally
1031
+ if (i < priorLocalLength) {
1032
+ const priorLocalMember = priorLocalState[i];
1033
+ if (priorLocalMember !== member) {
1034
+ changed = true;
1035
+ }
1036
+ }
1037
+ added.add(member);
1038
+ onAdd(member);
1039
+ }
1040
+ }
1041
+
1042
+ // accumulate anything removed
1043
+ if (i < prevLength) {
1044
+ const prevMember = prevState[i];
1045
+
1046
+ // detect reordering
1047
+ if (equalLength && member !== prevMember) {
1048
+ // the new remote order does not match the current remote order
1049
+ // indicating a change in membership or reordering
1050
+ remoteOrderChanged = true;
1051
+ // however: if the new remote order matches the current local order
1052
+ // we can disregard the change notification generation so long as
1053
+ // we are not configured to reset on remote update (which is deprecated)
1054
+
1055
+ if (i < priorLocalLength) {
1056
+ const priorLocalMember = priorLocalState[i];
1057
+ if (priorLocalMember !== member) {
1058
+ changed = true;
1059
+ }
1060
+ } else if (i < finalLength) {
1061
+ // if we have exceeded the length of priorLocalState and we are within the range
1062
+ // of the finalState then we must have changed
1063
+
1064
+ changed = true;
1065
+ }
1066
+
1067
+ // if remote order hasn't changed but local order differs
1068
+ // and we are configured to reset on remote update (which is deprecated)
1069
+ // then we still need to mark the relationship as changed
1070
+ } else {
1071
+ if (remoteClearsLocal) {
1072
+ if (equalLength && !changed && i < priorLocalLength) {
1073
+ const priorLocalMember = priorLocalState[i];
1074
+ if (priorLocalMember !== prevMember) {
1075
+ changed = true;
1076
+ }
1077
+ }
1078
+ }
1079
+ }
1080
+ if (!finalSet.has(prevMember)) {
1081
+ // if we are within finalLength, we can only be "changed" if we've already exceeded
1082
+ // the index range of priorLocalState, as otherwise the previous member may still
1083
+ // be removed.
1084
+ //
1085
+ // prior local: [1, 2, 3, 4]
1086
+ // final state: [1, 2, 3]
1087
+ // prev remote state: [1, 2, 5, 3, 4]
1088
+ // i === 2
1089
+ // prevMember === 5
1090
+ // !finalSet.has(prevMember) === true
1091
+ //
1092
+ // because we will become changed at i===3,
1093
+ // we do not need to worry about becoming changed at i===2
1094
+ // as the arrays until now are still the same
1095
+ //
1096
+ // prior local: [1, 2, 3]
1097
+ // final state: [1, 2, 3, 4]
1098
+ // prev remote state: [1, 2, 5, 3, 4]
1099
+ // i === 2
1100
+ // prevMember === 5
1101
+ // !finalSet.has(prevMember) === true
1102
+ //
1103
+ // because we will become changed at i===3
1104
+ // we do not need to worry about becoming changed at i===2
1105
+ //
1106
+ // prior local: [1, 2, 3]
1107
+ // final state: [1, 2, 3]
1108
+ // prev remote state: [1, 2, 5, 3, 4]
1109
+ // i === 2
1110
+ // prevMember === 5
1111
+ // !finalSet.has(prevMember) === true
1112
+ //
1113
+ // because we have same length and same membership order
1114
+ // we do not need to worry about becoming changed at i===2
1115
+ //
1116
+ // if you do not have a priorLocalState you can't be changed
1117
+ // ergo, we never need to set changed in this branch.
1118
+ // this log can still be useful for debugging.
1119
+
1120
+ //
1121
+ // we do still set remoteOrderChanged as it has
1122
+ remoteOrderChanged = true;
1123
+ removed.add(prevMember);
1124
+ onDel(prevMember);
1125
+ }
1126
+ }
1127
+ }
1128
+ return {
1129
+ add: added,
1130
+ del: removed,
1131
+ finalState,
1132
+ finalSet,
1133
+ changed,
1134
+ remoteOrderChanged
1135
+ };
1136
+ }
1137
+ function diffCollection(finalState, relationship, onAdd, onDel) {
1138
+ const finalSet = new Set(finalState);
1139
+ const {
1140
+ localState: priorLocalState,
1141
+ remoteState,
1142
+ remoteMembers
1143
+ } = relationship;
1144
+ {
1145
+ if (finalState.length !== finalSet.size) {
1146
+ const {
1147
+ diff
1148
+ } = _deprecatedCompare(priorLocalState, finalState, finalSet, remoteState, remoteMembers, onAdd, onDel, relationship.definition.resetOnRemoteUpdate);
1149
+ return diff;
1150
+ }
1151
+ }
1152
+ return _compare(priorLocalState, finalState, finalSet, remoteState, remoteMembers, onAdd, onDel, relationship.definition.resetOnRemoteUpdate);
1153
+ }
1154
+ function computeLocalState(storage) {
1155
+ if (!storage.isDirty) {
1156
+ return storage.localState;
1157
+ }
1158
+ const state = storage.remoteState.slice();
1159
+ storage.removals?.forEach(v => {
1160
+ const index = state.indexOf(v);
1161
+ state.splice(index, 1);
1162
+ });
1163
+ storage.additions?.forEach(v => {
1164
+ state.push(v);
1165
+ });
1166
+ storage.localState = state;
1167
+ storage.isDirty = false;
1168
+ return state;
1169
+ }
1170
+
1171
+ /**
1172
+ * A function which attempts to add a value to the local state of a collection
1173
+ * relationship, and returns true if the value was added, or false if it was
1174
+ * already present.
1175
+ *
1176
+ * It will not generate a notification, will not update the relationships to dirty,
1177
+ * and will not update the inverse relationships, making it suitable for use as
1178
+ * an internal util to perform the just the addition to a specific side of a
1179
+ * relationship.
1180
+ *
1181
+ * @internal
1182
+ */
1183
+ function _add(graph, record, relationship, value, index, isRemote) {
1184
+ return !isRemote ? _addLocal(graph, record, relationship, value, index) : _addRemote(graph, record, relationship, value, index);
1185
+ }
1186
+ function _addRemote(graph, record, relationship, value, index) {
1187
+ const {
1188
+ remoteMembers,
1189
+ additions,
1190
+ removals,
1191
+ remoteState
1192
+ } = relationship;
1193
+ if (remoteMembers.has(value)) {
1194
+ return false;
1195
+ }
1196
+
1197
+ // add to the remote state
1198
+ remoteMembers.add(value);
1199
+ const hasValidIndex = index !== null && index >= 0 && index < remoteState.length;
1200
+ if (hasValidIndex) {
1201
+ remoteState.splice(index, 0, value);
1202
+ } else {
1203
+ remoteState.push(value);
1204
+ }
1205
+
1206
+ // remove from additions if present
1207
+ if (additions?.has(value)) {
1208
+ additions.delete(value);
1209
+
1210
+ // nothing more to do this was our state already
1211
+ return false;
1212
+ }
1213
+
1214
+ // if the relationship already needs to recalc, we don't bother
1215
+ // attempting to patch the localState
1216
+ if (relationship.isDirty) {
1217
+ return true;
1218
+ }
1219
+
1220
+ // if we have existing localState
1221
+ // we attempt to patch it without blowing it away
1222
+ // as this is more efficient than recomputing
1223
+ // it allows us to preserve local ordering
1224
+ // to a small extent. Local ordering should not
1225
+ // be relied upon as any remote change could blow it away
1226
+ if (relationship.localState) {
1227
+ if (!hasValidIndex) {
1228
+ relationship.localState.push(value);
1229
+ } else if (index === 0) {
1230
+ relationship.localState.unshift(value);
1231
+ } else if (!removals?.size) {
1232
+ relationship.localState.splice(index, 0, value);
1233
+ } else {
1234
+ relationship.isDirty = true;
1235
+ }
1236
+ }
1237
+ return true;
1238
+ }
1239
+ function _addLocal(graph, record, relationship, value, index) {
1240
+ const {
1241
+ remoteMembers,
1242
+ removals
1243
+ } = relationship;
1244
+ let additions = relationship.additions;
1245
+ const hasPresence = remoteMembers.has(value) || additions?.has(value);
1246
+ if (hasPresence && !removals?.has(value)) {
1247
+ return false;
1248
+ }
1249
+ if (removals?.has(value)) {
1250
+ removals.delete(value);
1251
+ } else {
1252
+ if (!additions) {
1253
+ additions = relationship.additions = new Set();
1254
+ }
1255
+ relationship.state.hasReceivedData = true;
1256
+ additions.add(value);
1257
+ const {
1258
+ type
1259
+ } = relationship.definition;
1260
+ if (type !== value.type) {
1261
+ graph.registerPolymorphicType(value.type, type);
1262
+ }
1263
+ }
1264
+
1265
+ // if we have existing localState
1266
+ // and we have an index
1267
+ // apply the change, as this is more efficient
1268
+ // than recomputing localState and
1269
+ // it allows us to preserve local ordering
1270
+ // to a small extend. Local ordering should not
1271
+ // be relied upon as any remote change will blow it away
1272
+ if (relationship.localState) {
1273
+ if (index !== null) {
1274
+ relationship.localState.splice(index, 0, value);
1275
+ } else {
1276
+ relationship.localState.push(value);
1277
+ }
1278
+ }
1279
+ return true;
1280
+ }
1281
+ function _remove(graph, record, relationship, value, index, isRemote) {
1282
+ return !isRemote ? _removeLocal(relationship, value) : _removeRemote(relationship, value);
1283
+ }
1284
+ function _removeLocal(relationship, value) {
1285
+ const {
1286
+ remoteMembers,
1287
+ additions
1288
+ } = relationship;
1289
+ let removals = relationship.removals;
1290
+ const hasPresence = remoteMembers.has(value) || additions?.has(value);
1291
+ if (!hasPresence || removals?.has(value)) {
1292
+ return false;
1293
+ }
1294
+ if (additions?.has(value)) {
1295
+ additions.delete(value);
1296
+ } else {
1297
+ if (!removals) {
1298
+ removals = relationship.removals = new Set();
1299
+ }
1300
+ removals.add(value);
1301
+ }
1302
+
1303
+ // if we have existing localState
1304
+ // apply the change, as this is more efficient
1305
+ // than recomputing localState and
1306
+ // it allows us to preserve local ordering
1307
+ // to a small extend. Local ordering should not
1308
+ // be relied upon as any remote change will blow it away
1309
+ if (relationship.localState) {
1310
+ const index = relationship.localState.indexOf(value);
1311
+ relationship.localState.splice(index, 1);
1312
+ }
1313
+ return true;
1314
+ }
1315
+ function _removeRemote(relationship, value) {
1316
+ const {
1317
+ remoteMembers,
1318
+ additions,
1319
+ removals,
1320
+ remoteState
1321
+ } = relationship;
1322
+ if (!remoteMembers.has(value)) {
1323
+ return false;
1324
+ }
1325
+
1326
+ // remove from remote state
1327
+ remoteMembers.delete(value);
1328
+ let index = remoteState.indexOf(value);
1329
+ remoteState.splice(index, 1);
1330
+
1331
+ // remove from removals if present
1332
+ if (removals?.has(value)) {
1333
+ removals.delete(value);
1334
+
1335
+ // nothing more to do this was our state already
1336
+ return false;
1337
+ }
1338
+
1339
+ // if we have existing localState
1340
+ // and we have an index
1341
+ // apply the change, as this is more efficient
1342
+ // than recomputing localState and
1343
+ // it allows us to preserve local ordering
1344
+ // to a small extend. Local ordering should not
1345
+ // be relied upon as any remote change will blow it away
1346
+ if (relationship.localState) {
1347
+ index = relationship.localState.indexOf(value);
1348
+ relationship.localState.splice(index, 1);
1349
+ }
1350
+ return true;
1351
+ }
1352
+ function rollbackRelationship(graph, key, field, relationship) {
1353
+ if (isBelongsTo(relationship)) {
1354
+ replaceRelatedRecord(graph, {
1355
+ record: key,
1356
+ field,
1357
+ value: relationship.remoteState
1358
+ }, false);
1359
+ } else {
1360
+ replaceRelatedRecords(graph, {
1361
+ record: key,
1362
+ field,
1363
+ value: relationship.remoteState.slice()
1364
+ }, false);
1365
+
1366
+ // when the change was a "reorder" only we wont have generated
1367
+ // a notification yet.
1368
+ // if we give rollback a unique operation we can use the ability of
1369
+ // diff to report a separate `remoteOrderChanged` flag to trigger this
1370
+ // if needed to avoid the duplicate.
1371
+ notifyChange(graph, relationship);
1372
+ }
1373
+ }
1374
+ function createState() {
1375
+ return {
1376
+ hasReceivedData: false,
1377
+ isEmpty: true,
1378
+ isStale: false,
1379
+ hasFailedLoadAttempt: false,
1380
+ shouldForceReload: false,
1381
+ hasDematerializedInverse: false
1382
+ };
1383
+ }
1384
+ function createCollectionEdge(definition, identifier) {
1385
+ return {
1386
+ definition,
1387
+ identifier,
1388
+ state: createState(),
1389
+ remoteMembers: new Set(),
1390
+ remoteState: [],
1391
+ additions: null,
1392
+ removals: null,
1393
+ meta: null,
1394
+ links: null,
1395
+ localState: null,
1396
+ isDirty: false,
1397
+ transactionRef: 0,
1398
+ accessed: false,
1399
+ _diff: undefined
1400
+ };
1401
+ }
1402
+ const cp = structuredClone;
1403
+ function legacyGetCollectionRelationshipData(source, getRemoteState) {
1404
+ source.accessed = true;
1405
+ const payload = {};
1406
+ if (source.state.hasReceivedData) {
1407
+ payload.data = getRemoteState ? source.remoteState.slice() : computeLocalState(source);
1408
+ }
1409
+ if (source.links) {
1410
+ payload.links = cp(source.links);
1411
+ }
1412
+ if (source.meta) {
1413
+ payload.meta = cp(source.meta);
1414
+ }
1415
+ return payload;
1416
+ }
1417
+
1418
+ /**
1419
+ Implicit relationships are relationships which have not been declared but the inverse side exists on
1420
+ another record somewhere
1421
+
1422
+ For example consider the following two models
1423
+
1424
+ ::: code-group
1425
+
1426
+ ```js [./models/comment.js]
1427
+ import { Model, attr } from '@warp-drive/legacy/model';
1428
+
1429
+ export default class Comment extends Model {
1430
+ @attr text;
1431
+ }
1432
+ ```
1433
+
1434
+ ```js [./models/post.js]
1435
+ import { Model, attr, hasMany } from '@warp-drive/legacy/model';
1436
+
1437
+ export default class Post extends Model {
1438
+ @attr title;
1439
+ @hasMany('comment', { async: true, inverse: null }) comments;
1440
+ }
1441
+ ```
1442
+
1443
+ :::
1444
+
1445
+ Then we would have a implicit 'post' relationship for the comment record in order
1446
+ to be do things like remove the comment from the post if the comment were to be deleted.
1447
+ */
1448
+
1449
+ function createImplicitEdge(definition, identifier) {
1450
+ return {
1451
+ definition,
1452
+ identifier,
1453
+ localMembers: new Set(),
1454
+ remoteMembers: new Set()
1455
+ };
1456
+ }
1457
+
1458
+ /**
1459
+ * Stores the data for one side of a "single" resource relationship.
1460
+ *
1461
+ * @private
1462
+ */
1463
+
1464
+ function createResourceEdge(definition, identifier) {
1465
+ return {
1466
+ definition,
1467
+ identifier,
1468
+ state: createState(),
1469
+ transactionRef: 0,
1470
+ localState: null,
1471
+ remoteState: null,
1472
+ meta: null,
1473
+ links: null,
1474
+ accessed: false
1475
+ };
1476
+ }
1477
+ function legacyGetResourceRelationshipData(source, getRemoteState) {
1478
+ source.accessed = true;
1479
+ let data;
1480
+ const payload = {};
1481
+ if (getRemoteState && source.remoteState) {
1482
+ data = source.remoteState;
1483
+ } else if (!getRemoteState && source.localState) {
1484
+ data = source.localState;
1485
+ }
1486
+ if ((getRemoteState && source.remoteState === null || source.localState === null) && source.state.hasReceivedData) {
1487
+ data = null;
1488
+ }
1489
+ if (source.links) {
1490
+ payload.links = source.links;
1491
+ }
1492
+ if (data !== undefined) {
1493
+ payload.data = data;
1494
+ }
1495
+ if (source.meta) {
1496
+ payload.meta = source.meta;
1497
+ }
1498
+ return payload;
1499
+ }
1500
+ function addToRelatedRecords(graph, op, isRemote) {
1501
+ const {
1502
+ record,
1503
+ value,
1504
+ index
1505
+ } = op;
1506
+ const relationship = graph.get(record, op.field);
1507
+ const _isBelongsTo = isBelongsTo(relationship);
1508
+ if (isRemote && _isBelongsTo) {
1509
+ if (value !== relationship.remoteState) {
1510
+ const newOp = {
1511
+ record,
1512
+ field: op.field,
1513
+ value: value
1514
+ };
1515
+ return replaceRelatedRecord(graph, newOp, isRemote);
1516
+ }
1517
+ return;
1518
+ }
1519
+
1520
+ // if we are not dirty but have a null localState then we
1521
+ // are mutating a relationship that has never been fetched
1522
+ // so we initialize localState to an empty array
1523
+ if (!relationship.isDirty && !relationship.localState) {
1524
+ relationship.localState = [];
1525
+ }
1526
+ if (Array.isArray(value)) {
1527
+ for (let i = 0; i < value.length; i++) {
1528
+ addRelatedRecord(graph, relationship, record, value[i], index !== undefined ? index + i : null, isRemote);
1529
+ }
1530
+ } else {
1531
+ addRelatedRecord(graph, relationship, record, value, index ?? null, isRemote);
1532
+ }
1533
+ notifyChange(graph, relationship);
1534
+ }
1535
+ function addRelatedRecord(graph, relationship, record, value, index, isRemote) {
1536
+ if (_add(graph, record, relationship, value, index, isRemote)) {
1537
+ addToInverse(graph, value, relationship.definition.inverseKey, record, isRemote);
1538
+ }
1539
+ }
1540
+ function mergeIdentifier(graph, op, relationships) {
1541
+ Object.keys(relationships).forEach(key => {
1542
+ const rel = relationships[key];
1543
+ if (!rel) {
1544
+ return;
1545
+ }
1546
+ mergeIdentifierForRelationship(graph, op, rel);
1547
+ });
1548
+ }
1549
+ function mergeIdentifierForRelationship(graph, op, rel) {
1550
+ rel.identifier = op.value;
1551
+ forAllRelatedIdentifiers(rel, identifier => {
1552
+ const inverse = graph.get(identifier, rel.definition.inverseKey);
1553
+ mergeInRelationship(graph, inverse, op);
1554
+ });
1555
+ }
1556
+ function mergeInRelationship(graph, rel, op) {
1557
+ if (isBelongsTo(rel)) {
1558
+ mergeBelongsTo(graph, rel, op);
1559
+ } else if (isHasMany(rel)) {
1560
+ mergeHasMany(graph, rel, op);
1561
+ } else {
1562
+ mergeImplicit(graph, rel, op);
1563
+ }
1564
+ }
1565
+ function mergeBelongsTo(graph, rel, op) {
1566
+ if (rel.remoteState === op.record) {
1567
+ rel.remoteState = op.value;
1568
+ }
1569
+ if (rel.localState === op.record) {
1570
+ rel.localState = op.value;
1571
+ notifyChange(graph, rel);
1572
+ }
1573
+ }
1574
+ function mergeHasMany(graph, rel, op) {
1575
+ if (rel.remoteMembers.has(op.record)) {
1576
+ rel.remoteMembers.delete(op.record);
1577
+ rel.remoteMembers.add(op.value);
1578
+ const index = rel.remoteState.indexOf(op.record);
1579
+ rel.remoteState.splice(index, 1, op.value);
1580
+ rel.isDirty = true;
1581
+ }
1582
+ if (rel.additions?.has(op.record)) {
1583
+ rel.additions.delete(op.record);
1584
+ rel.additions.add(op.value);
1585
+ rel.isDirty = true;
1586
+ }
1587
+ if (rel.removals?.has(op.record)) {
1588
+ rel.removals.delete(op.record);
1589
+ rel.removals.add(op.value);
1590
+ rel.isDirty = true;
1591
+ }
1592
+ if (rel.isDirty) {
1593
+ notifyChange(graph, rel);
1594
+ }
1595
+ }
1596
+ function mergeImplicit(graph, rel, op) {
1597
+ if (rel.remoteMembers.has(op.record)) {
1598
+ rel.remoteMembers.delete(op.record);
1599
+ rel.remoteMembers.add(op.value);
1600
+ }
1601
+ if (rel.localMembers.has(op.record)) {
1602
+ rel.localMembers.delete(op.record);
1603
+ rel.localMembers.add(op.value);
1604
+ }
1605
+ }
1606
+ function removeFromRelatedRecords(graph, op, isRemote) {
1607
+ const {
1608
+ record,
1609
+ value
1610
+ } = op;
1611
+ const relationship = graph.get(record, op.field);
1612
+ const _isBelongsTo = isBelongsTo(relationship);
1613
+ if (isRemote && _isBelongsTo) {
1614
+ if (value === relationship.remoteState) {
1615
+ const newOp = {
1616
+ record,
1617
+ field: op.field,
1618
+ value: null
1619
+ };
1620
+ return replaceRelatedRecord(graph, newOp, isRemote);
1621
+ }
1622
+ return;
1623
+ }
1624
+ if (Array.isArray(value)) {
1625
+ for (let i = 0; i < value.length; i++) {
1626
+ removeRelatedRecord(graph, record, relationship, value[i], op.index ?? null, isRemote);
1627
+ }
1628
+ } else {
1629
+ removeRelatedRecord(graph, record, relationship, value, op.index ?? null, isRemote);
1630
+ }
1631
+ notifyChange(graph, relationship);
1632
+ }
1633
+ function removeRelatedRecord(graph, record, relationship, value, index, isRemote) {
1634
+ if (_remove(graph, record, relationship, value, index, isRemote)) {
1635
+ removeFromInverse(graph, value, relationship.definition.inverseKey, record, isRemote);
1636
+ }
1637
+ }
1638
+
1639
+ /*
1640
+ This method normalizes a link to an "links object". If the passed link is
1641
+ already an object it's returned without any modifications.
1642
+
1643
+ See http://jsonapi.org/format/#document-links for more information.
1644
+ */
1645
+ function _normalizeLink(link) {
1646
+ switch (typeof link) {
1647
+ case 'object':
1648
+ return link;
1649
+ case 'string':
1650
+ return {
1651
+ href: link
1652
+ };
1653
+ }
1654
+ }
1655
+
1656
+ /*
1657
+ Updates the "canonical" or "remote" state of a relationship, replacing any existing
1658
+ state and blowing away any local changes (excepting new records).
1659
+ */
1660
+ function updateRelationshipOperation(graph, op) {
1661
+ const relationship = graph.get(op.record, op.field);
1662
+ const {
1663
+ definition,
1664
+ state,
1665
+ identifier
1666
+ } = relationship;
1667
+ const {
1668
+ isCollection
1669
+ } = definition;
1670
+ const payload = op.value;
1671
+ let hasRelationshipDataProperty = false;
1672
+ let hasUpdatedLink = false;
1673
+ if (payload.meta) {
1674
+ relationship.meta = payload.meta;
1675
+ }
1676
+ if (payload.data !== undefined) {
1677
+ hasRelationshipDataProperty = true;
1678
+ if (isCollection) {
1679
+ // TODO deprecate this case. We
1680
+ // have tests saying we support it.
1681
+ if (payload.data === null) {
1682
+ payload.data = [];
1683
+ }
1684
+ const cache = graph.store.cacheKeyManager;
1685
+ graph.update({
1686
+ op: 'replaceRelatedRecords',
1687
+ record: identifier,
1688
+ field: op.field,
1689
+ value: upgradeIdentifiers(payload.data, cache)
1690
+ }, true);
1691
+ } else {
1692
+ graph.update({
1693
+ op: 'replaceRelatedRecord',
1694
+ record: identifier,
1695
+ field: op.field,
1696
+ value: payload.data ? graph.store.cacheKeyManager.upgradeIdentifier(payload.data) : null
1697
+ }, true);
1698
+ }
1699
+ } else if (definition.isAsync === false && !state.hasReceivedData) {
1700
+ hasRelationshipDataProperty = true;
1701
+ if (isCollection) {
1702
+ graph.update({
1703
+ op: 'replaceRelatedRecords',
1704
+ record: identifier,
1705
+ field: op.field,
1706
+ value: []
1707
+ }, true);
1708
+ } else {
1709
+ graph.update({
1710
+ op: 'replaceRelatedRecord',
1711
+ record: identifier,
1712
+ field: op.field,
1713
+ value: null
1714
+ }, true);
1715
+ }
1716
+ }
1717
+ if (payload.links) {
1718
+ const originalLinks = relationship.links;
1719
+ relationship.links = payload.links;
1720
+ if (payload.links.related) {
1721
+ const relatedLink = _normalizeLink(payload.links.related);
1722
+ const currentLink = originalLinks && originalLinks.related ? _normalizeLink(originalLinks.related) : null;
1723
+ const currentLinkHref = currentLink ? currentLink.href : null;
1724
+ if (relatedLink && relatedLink.href && relatedLink.href !== currentLinkHref) {
1725
+ hasUpdatedLink = true;
1726
+ }
1727
+ }
1728
+ }
1729
+
1730
+ /*
1731
+ Data being pushed into the relationship might contain only data or links,
1732
+ or a combination of both.
1733
+ IF contains only data
1734
+ IF contains both links and data
1735
+ state.isEmpty -> true if is empty array (has-many) or is null (belongs-to)
1736
+ state.hasReceivedData -> true
1737
+ hasDematerializedInverse -> false
1738
+ state.isStale -> false
1739
+ allInverseRecordsAreLoaded -> run-check-to-determine
1740
+ IF contains only links
1741
+ state.isStale -> true
1742
+ */
1743
+ relationship.state.hasFailedLoadAttempt = false;
1744
+ if (hasRelationshipDataProperty) {
1745
+ const relationshipIsEmpty = payload.data === null || Array.isArray(payload.data) && payload.data.length === 0;
1746
+
1747
+ // we don't need to notify here as the update op we pushed in above will notify once
1748
+ // membership is in the correct state.
1749
+ relationship.state.hasReceivedData = true;
1750
+ relationship.state.isStale = false;
1751
+ relationship.state.hasDematerializedInverse = false;
1752
+ relationship.state.isEmpty = relationshipIsEmpty;
1753
+ } else if (hasUpdatedLink) {
1754
+ // only notify stale if we have not previously received membership data.
1755
+ // within this same transaction
1756
+ // this prevents refetching when only one side of the relationship in the
1757
+ // payload contains the info while the other side contains just a link
1758
+ // this only works when the side with just a link is a belongsTo, as we
1759
+ // don't know if a hasMany has full information or not.
1760
+ // see #7049 for context.
1761
+ if (isCollection || !relationship.state.hasReceivedData || isStaleTransaction(relationship.transactionRef, graph._transaction)) {
1762
+ relationship.state.isStale = true;
1763
+ notifyChange(graph, relationship);
1764
+ } else {
1765
+ relationship.state.isStale = false;
1766
+ }
1767
+ }
1768
+ }
1769
+ function isStaleTransaction(relationshipTransactionId, graphTransactionId) {
1770
+ return relationshipTransactionId === 0 ||
1771
+ // relationship has never notified
1772
+ graphTransactionId === null ||
1773
+ // we are not in a transaction
1774
+ relationshipTransactionId < graphTransactionId // we are not part of the current transaction
1775
+ ;
1776
+ }
1777
+ function upgradeIdentifiers(arr, cache) {
1778
+ for (let i = 0; i < arr.length; i++) {
1779
+ arr[i] = cache.upgradeIdentifier(arr[i]);
1780
+ }
1781
+ return arr;
1782
+ }
1783
+ const Graphs = getOrSetGlobal('Graphs', new Map());
1784
+ /*
1785
+ * Graph acts as the cache for relationship data. It allows for
1786
+ * us to ask about and update relationships for a given Identifier
1787
+ * without requiring other objects for that Identifier to be
1788
+ * instantiated (such as `RecordData` or a `Record`)
1789
+ *
1790
+ * This also allows for us to make more substantive changes to relationships
1791
+ * with increasingly minor alterations to other portions of the internals
1792
+ * over time.
1793
+ *
1794
+ * The graph is made up of nodes and edges. Each unique identifier gets
1795
+ * its own node, which is a dictionary with a list of that node's edges
1796
+ * (or connections) to other nodes. In `Model` terms, a node represents a
1797
+ * record instance, with each key (an edge) in the dictionary correlating
1798
+ * to either a `hasMany` or `belongsTo` field on that record instance.
1799
+ *
1800
+ * The value for each key, or `edge` is the identifier(s) the node relates
1801
+ * to in the graph from that key.
1802
+ */
1803
+ class Graph {
1804
+ constructor(store) {
1805
+ this._definitionCache = Object.create(null);
1806
+ this._metaCache = Object.create(null);
1807
+ this._potentialPolymorphicTypes = Object.create(null);
1808
+ this.identifiers = new Map();
1809
+ this.store = store;
1810
+ this._realStore = store._store;
1811
+ this.isDestroyed = false;
1812
+ this._willSyncRemote = false;
1813
+ this._willSyncLocal = false;
1814
+ this._pushedUpdates = {
1815
+ belongsTo: undefined,
1816
+ hasMany: undefined,
1817
+ deletions: []
1818
+ };
1819
+ this._updatedRelationships = new Set();
1820
+ this._transaction = null;
1821
+ this._removing = null;
1822
+ this.silenceNotifications = false;
1823
+ }
1824
+ has(resourceKey, propertyName) {
1825
+ const relationships = this.identifiers.get(resourceKey);
1826
+ if (!relationships) {
1827
+ return false;
1828
+ }
1829
+ return relationships[propertyName] !== undefined;
1830
+ }
1831
+ getDefinition(resourceKey, propertyName) {
1832
+ let defs = this._metaCache[resourceKey.type];
1833
+ let meta = defs?.[propertyName];
1834
+ if (!meta) {
1835
+ const info = /*#__NOINLINE__*/upgradeDefinition(this, resourceKey, propertyName);
1836
+
1837
+ // if (info.rhs_definition?.kind === 'implicit') {
1838
+ // we should possibly also do this
1839
+ // but it would result in being extremely permissive for other relationships by accident
1840
+ // this.registerPolymorphicType(info.rhs_baseModelName, identifier.type);
1841
+ // }
1842
+
1843
+ meta = /*#__NOINLINE__*/isLHS(info, resourceKey.type, propertyName) ? info.lhs_definition : info.rhs_definition;
1844
+ defs = this._metaCache[resourceKey.type] = defs || {};
1845
+ defs[propertyName] = meta;
1846
+ }
1847
+ return meta;
1848
+ }
1849
+ get(resourceKey, propertyName) {
1850
+ let relationships = this.identifiers.get(resourceKey);
1851
+ if (!relationships) {
1852
+ relationships = Object.create(null);
1853
+ this.identifiers.set(resourceKey, relationships);
1854
+ }
1855
+ let relationship = relationships[propertyName];
1856
+ if (!relationship) {
1857
+ const meta = this.getDefinition(resourceKey, propertyName);
1858
+ if (meta.kind === 'belongsTo') {
1859
+ relationship = relationships[propertyName] = createResourceEdge(meta, resourceKey);
1860
+ } else if (meta.kind === 'hasMany') {
1861
+ relationship = relationships[propertyName] = createCollectionEdge(meta, resourceKey);
1862
+ } else {
1863
+ relationship = relationships[propertyName] = createImplicitEdge(meta, resourceKey);
1864
+ }
1865
+ }
1866
+ return relationship;
1867
+ }
1868
+ getData(resourceKey, propertyName) {
1869
+ const relationship = this.get(resourceKey, propertyName);
1870
+ if (isBelongsTo(relationship)) {
1871
+ return legacyGetResourceRelationshipData(relationship, false);
1872
+ }
1873
+ return legacyGetCollectionRelationshipData(relationship, false);
1874
+ }
1875
+ getRemoteData(resourceKey, propertyName) {
1876
+ const relationship = this.get(resourceKey, propertyName);
1877
+ if (isBelongsTo(relationship)) {
1878
+ return legacyGetResourceRelationshipData(relationship, true);
1879
+ }
1880
+ return legacyGetCollectionRelationshipData(relationship, true);
1881
+ }
1882
+
1883
+ /*
1884
+ * Allows for the graph to dynamically discover polymorphic connections
1885
+ * without needing to walk prototype chains.
1886
+ *
1887
+ * Used by edges when an added `type` does not match the expected `type`
1888
+ * for that edge.
1889
+ *
1890
+ * Currently we assert before calling this. For a public API we will want
1891
+ * to call out to the schema manager to ask if we should consider these
1892
+ * types as equivalent for a given relationship.
1893
+ */
1894
+ registerPolymorphicType(type1, type2) {
1895
+ const typeCache = this._potentialPolymorphicTypes;
1896
+ let t1 = typeCache[type1];
1897
+ if (!t1) {
1898
+ t1 = typeCache[type1] = Object.create(null);
1899
+ }
1900
+ t1[type2] = true;
1901
+ let t2 = typeCache[type2];
1902
+ if (!t2) {
1903
+ t2 = typeCache[type2] = Object.create(null);
1904
+ }
1905
+ t2[type1] = true;
1906
+ }
1907
+ isReleasable(resourceKey) {
1908
+ const relationships = this.identifiers.get(resourceKey);
1909
+ if (!relationships) {
1910
+ return true;
1911
+ }
1912
+ const keys = Object.keys(relationships);
1913
+ for (let i = 0; i < keys.length; i++) {
1914
+ const relationship = relationships[keys[i]];
1915
+ // account for previously unloaded relationships
1916
+ // typically from a prior deletion of a record that pointed to this one implicitly
1917
+ if (relationship === undefined) {
1918
+ continue;
1919
+ }
1920
+ if (relationship.definition.inverseIsAsync && !checkIfNew(this._realStore, resourceKey)) {
1921
+ return false;
1922
+ }
1923
+ }
1924
+ return true;
1925
+ }
1926
+ unload(resourceKey, silenceNotifications) {
1927
+ const relationships = this.identifiers.get(resourceKey);
1928
+ if (relationships) {
1929
+ // cleans up the graph but retains some nodes
1930
+ // to allow for rematerialization
1931
+ Object.keys(relationships).forEach(key => {
1932
+ const rel = relationships[key];
1933
+ if (!rel) {
1934
+ return;
1935
+ }
1936
+ /*#__NOINLINE__*/
1937
+ destroyRelationship(this, rel, silenceNotifications);
1938
+ if (/*#__NOINLINE__*/isImplicit(rel)) {
1939
+ // @ts-expect-error
1940
+ relationships[key] = undefined;
1941
+ }
1942
+ });
1943
+ }
1944
+ }
1945
+ _isDirty(resourceKey, field) {
1946
+ const relationships = this.identifiers.get(resourceKey);
1947
+ if (!relationships) {
1948
+ return false;
1949
+ }
1950
+ const relationship = relationships[field];
1951
+ if (!relationship) {
1952
+ return false;
1953
+ }
1954
+ if (isBelongsTo(relationship)) {
1955
+ return relationship.localState !== relationship.remoteState;
1956
+ } else if (isHasMany(relationship)) {
1957
+ const hasAdditions = relationship.additions !== null && relationship.additions.size > 0;
1958
+ const hasRemovals = relationship.removals !== null && relationship.removals.size > 0;
1959
+ return hasAdditions || hasRemovals || isReordered(relationship);
1960
+ }
1961
+ return false;
1962
+ }
1963
+ getChanged(resourceKey) {
1964
+ const relationships = this.identifiers.get(resourceKey);
1965
+ const changed = new Map();
1966
+ if (!relationships) {
1967
+ return changed;
1968
+ }
1969
+ const keys = Object.keys(relationships);
1970
+ for (let i = 0; i < keys.length; i++) {
1971
+ const field = keys[i];
1972
+ const relationship = relationships[field];
1973
+ if (!relationship) {
1974
+ continue;
1975
+ }
1976
+ if (isBelongsTo(relationship)) {
1977
+ if (relationship.localState !== relationship.remoteState) {
1978
+ changed.set(field, {
1979
+ kind: 'resource',
1980
+ remoteState: relationship.remoteState,
1981
+ localState: relationship.localState
1982
+ });
1983
+ }
1984
+ } else if (isHasMany(relationship)) {
1985
+ const hasAdditions = relationship.additions !== null && relationship.additions.size > 0;
1986
+ const hasRemovals = relationship.removals !== null && relationship.removals.size > 0;
1987
+ const reordered = isReordered(relationship);
1988
+ if (hasAdditions || hasRemovals || reordered) {
1989
+ changed.set(field, {
1990
+ kind: 'collection',
1991
+ additions: new Set(relationship.additions),
1992
+ removals: new Set(relationship.removals),
1993
+ remoteState: relationship.remoteState,
1994
+ localState: legacyGetCollectionRelationshipData(relationship, false).data || [],
1995
+ reordered
1996
+ });
1997
+ }
1998
+ }
1999
+ }
2000
+ return changed;
2001
+ }
2002
+ hasChanged(resourceKey) {
2003
+ const relationships = this.identifiers.get(resourceKey);
2004
+ if (!relationships) {
2005
+ return false;
2006
+ }
2007
+ const keys = Object.keys(relationships);
2008
+ for (let i = 0; i < keys.length; i++) {
2009
+ if (this._isDirty(resourceKey, keys[i])) {
2010
+ return true;
2011
+ }
2012
+ }
2013
+ return false;
2014
+ }
2015
+ rollback(resourceKey) {
2016
+ const relationships = this.identifiers.get(resourceKey);
2017
+ const changed = [];
2018
+ if (!relationships) {
2019
+ return changed;
2020
+ }
2021
+ const keys = Object.keys(relationships);
2022
+ for (let i = 0; i < keys.length; i++) {
2023
+ const field = keys[i];
2024
+ const relationship = relationships[field];
2025
+ if (!relationship) {
2026
+ continue;
2027
+ }
2028
+ if (this._isDirty(resourceKey, field)) {
2029
+ rollbackRelationship(this, resourceKey, field, relationship);
2030
+ changed.push(field);
2031
+ }
2032
+ }
2033
+ return changed;
2034
+ }
2035
+ remove(resourceKey) {
2036
+ this._removing = resourceKey;
2037
+ this.unload(resourceKey);
2038
+ this.identifiers.delete(resourceKey);
2039
+ this._removing = null;
2040
+ }
2041
+
2042
+ /*
2043
+ * Remote state changes
2044
+ */
2045
+ push(op) {
2046
+ if (op.op === 'deleteRecord') {
2047
+ this._pushedUpdates.deletions.push(op);
2048
+ } else {
2049
+ const definition = this.getDefinition(op.record, op.field);
2050
+ addPending(this._pushedUpdates, definition, op);
2051
+ }
2052
+ if (!this._willSyncRemote) {
2053
+ this._willSyncRemote = true;
2054
+ const store = getStore(this.store);
2055
+ if (!store._cbs) {
2056
+ store._run(() => this._flushRemoteQueue());
2057
+ } else {
2058
+ store._schedule('coalesce', () => this._flushRemoteQueue());
2059
+ }
2060
+ }
2061
+ }
2062
+
2063
+ /*
2064
+ * Local state changes
2065
+ */
2066
+
2067
+ update(op, isRemote = false) {
2068
+ switch (op.op) {
2069
+ case 'mergeIdentifiers':
2070
+ {
2071
+ const relationships = this.identifiers.get(op.record);
2072
+ if (relationships) {
2073
+ /*#__NOINLINE__*/mergeIdentifier(this, op, relationships);
2074
+ }
2075
+ break;
2076
+ }
2077
+ case 'update':
2078
+ case 'updateRelationship':
2079
+ /*#__NOINLINE__*/updateRelationshipOperation(this, op);
2080
+ break;
2081
+ case 'deleteRecord':
2082
+ {
2083
+ const identifier = op.record;
2084
+ const relationships = this.identifiers.get(identifier);
2085
+ if (relationships) {
2086
+ Object.keys(relationships).forEach(key => {
2087
+ const rel = relationships[key];
2088
+ if (!rel) {
2089
+ return;
2090
+ }
2091
+ // works together with the has check
2092
+ // @ts-expect-error
2093
+ relationships[key] = undefined;
2094
+ /*#__NOINLINE__*/
2095
+ removeCompletelyFromInverse(this, rel);
2096
+ });
2097
+ this.identifiers.delete(identifier);
2098
+ }
2099
+ break;
2100
+ }
2101
+ case 'replaceRelatedRecord':
2102
+ /*#__NOINLINE__*/replaceRelatedRecord(this, op, isRemote);
2103
+ break;
2104
+ case 'add':
2105
+ /*#__NOINLINE__*/addToRelatedRecords(this, op, isRemote);
2106
+ break;
2107
+ case 'remove':
2108
+ /*#__NOINLINE__*/removeFromRelatedRecords(this, op, isRemote);
2109
+ break;
2110
+ case 'replaceRelatedRecords':
2111
+ /*#__NOINLINE__*/replaceRelatedRecords(this, op, isRemote);
2112
+ break;
2113
+ }
2114
+ }
2115
+ _scheduleLocalSync(relationship) {
2116
+ this._updatedRelationships.add(relationship);
2117
+ if (!this._willSyncLocal) {
2118
+ this._willSyncLocal = true;
2119
+ getStore(this.store)._schedule('sync', () => this._flushLocalQueue());
2120
+ }
2121
+ }
2122
+ _flushRemoteQueue() {
2123
+ if (!this._willSyncRemote) {
2124
+ return;
2125
+ }
2126
+ let transactionRef = peekTransient('transactionRef') ?? 0;
2127
+ this._transaction = ++transactionRef;
2128
+ setTransient('transactionRef', transactionRef);
2129
+ this._willSyncRemote = false;
2130
+ const updates = this._pushedUpdates;
2131
+ const {
2132
+ deletions,
2133
+ hasMany,
2134
+ belongsTo
2135
+ } = updates;
2136
+ updates.deletions = [];
2137
+ updates.hasMany = undefined;
2138
+ updates.belongsTo = undefined;
2139
+ for (let i = 0; i < deletions.length; i++) {
2140
+ this.update(deletions[i], true);
2141
+ }
2142
+ if (hasMany) {
2143
+ flushPending(this, hasMany);
2144
+ }
2145
+ if (belongsTo) {
2146
+ flushPending(this, belongsTo);
2147
+ }
2148
+ this._transaction = null;
2149
+ }
2150
+ _addToTransaction(relationship) {
2151
+ relationship.transactionRef = this._transaction;
2152
+ }
2153
+ _flushLocalQueue() {
2154
+ if (!this._willSyncLocal) {
2155
+ return;
2156
+ }
2157
+ if (this.silenceNotifications) {
2158
+ this.silenceNotifications = false;
2159
+ this._updatedRelationships = new Set();
2160
+ return;
2161
+ }
2162
+ this._willSyncLocal = false;
2163
+ const updated = this._updatedRelationships;
2164
+ this._updatedRelationships = new Set();
2165
+ updated.forEach(rel => notifyChange(this, rel));
2166
+ }
2167
+ destroy() {
2168
+ Graphs.delete(this.store);
2169
+ this.identifiers.clear();
2170
+ this.store = null;
2171
+ this.isDestroyed = true;
2172
+ }
2173
+ }
2174
+ function flushPending(graph, ops) {
2175
+ for (const type of ops.values()) {
2176
+ for (const opList of type.values()) {
2177
+ flushPendingList(graph, opList);
2178
+ }
2179
+ }
2180
+ }
2181
+ function flushPendingList(graph, opList) {
2182
+ for (let i = 0; i < opList.length; i++) {
2183
+ graph.update(opList[i], true);
2184
+ }
2185
+ }
2186
+
2187
+ // Handle dematerialization for relationship `rel`. In all cases, notify the
2188
+ // relationship of the dematerialization: this is done so the relationship can
2189
+ // notify its inverse which needs to update state
2190
+ //
2191
+ // If the inverse is sync, unloading this record is treated as a client-side
2192
+ // delete, so we remove the inverse records from this relationship to
2193
+ // disconnect the graph. Because it's not async, we don't need to keep around
2194
+ // the identifier as an id-wrapper for references
2195
+ function destroyRelationship(graph, rel, silenceNotifications) {
2196
+ if (isImplicit(rel)) {
2197
+ if (graph.isReleasable(rel.identifier)) {
2198
+ /*#__NOINLINE__*/removeCompletelyFromInverse(graph, rel);
2199
+ }
2200
+ return;
2201
+ }
2202
+ const {
2203
+ identifier
2204
+ } = rel;
2205
+ const {
2206
+ inverseKey
2207
+ } = rel.definition;
2208
+ if (!rel.definition.inverseIsImplicit) {
2209
+ /*#__NOINLINE__*/forAllRelatedIdentifiers(rel, inverseIdentifer => /*#__NOINLINE__*/notifyInverseOfDematerialization(graph, inverseIdentifer, inverseKey, identifier, silenceNotifications));
2210
+ }
2211
+ if (!rel.definition.inverseIsImplicit && !rel.definition.inverseIsAsync) {
2212
+ rel.state.isStale = true;
2213
+ /*#__NOINLINE__*/
2214
+ clearRelationship(rel);
2215
+
2216
+ // necessary to clear relationships in the ui from dematerialized records
2217
+ // hasMany is managed by Model which calls `retreiveLatest` after
2218
+ // dematerializing the resource-cache instance.
2219
+ // but sync belongsTo requires this since they don't have a proxy to update.
2220
+ // so we have to notify so it will "update" to null.
2221
+ // we should discuss whether we still care about this, probably fine to just
2222
+ // leave the ui relationship populated since the record is destroyed and
2223
+ // internally we've fully cleaned up.
2224
+ if (!rel.definition.isAsync && !silenceNotifications) {
2225
+ /*#__NOINLINE__*/notifyChange(graph, rel);
2226
+ }
2227
+ }
2228
+ }
2229
+ function notifyInverseOfDematerialization(graph, inverseIdentifier, inverseKey, resourceKey, silenceNotifications) {
2230
+ if (!graph.has(inverseIdentifier, inverseKey)) {
2231
+ return;
2232
+ }
2233
+ const relationship = graph.get(inverseIdentifier, inverseKey);
2234
+
2235
+ // For remote members, it is possible that inverseRecordData has already been associated to
2236
+ // to another record. For such cases, do not dematerialize the inverseRecordData
2237
+ if (!isBelongsTo(relationship) || !relationship.localState || resourceKey === relationship.localState) {
2238
+ /*#__NOINLINE__*/removeDematerializedInverse(graph, relationship, resourceKey, silenceNotifications);
2239
+ }
2240
+ }
2241
+ function clearRelationship(relationship) {
2242
+ if (isBelongsTo(relationship)) {
2243
+ relationship.localState = null;
2244
+ relationship.remoteState = null;
2245
+ relationship.state.hasReceivedData = false;
2246
+ relationship.state.isEmpty = true;
2247
+ } else {
2248
+ relationship.remoteMembers.clear();
2249
+ relationship.remoteState = [];
2250
+ relationship.additions = null;
2251
+ relationship.removals = null;
2252
+ relationship.localState = null;
2253
+ }
2254
+ }
2255
+ function removeDematerializedInverse(graph, relationship, inverseIdentifier, silenceNotifications) {
2256
+ if (isBelongsTo(relationship)) {
2257
+ const localInverse = relationship.localState;
2258
+ if (!relationship.definition.isAsync || localInverse && checkIfNew(graph._realStore, localInverse)) {
2259
+ // unloading inverse of a sync relationship is treated as a client-side
2260
+ // delete, so actually remove the models don't merely invalidate the cp
2261
+ // cache.
2262
+ // if the record being unloaded only exists on the client, we similarly
2263
+ // treat it as a client side delete
2264
+ if (relationship.localState === localInverse && localInverse !== null) {
2265
+ relationship.localState = null;
2266
+ }
2267
+ if (relationship.remoteState === localInverse && localInverse !== null) {
2268
+ relationship.remoteState = null;
2269
+ relationship.state.hasReceivedData = true;
2270
+ relationship.state.isEmpty = true;
2271
+ if (relationship.localState && !checkIfNew(graph._realStore, relationship.localState)) {
2272
+ relationship.localState = null;
2273
+ }
2274
+ }
2275
+ } else {
2276
+ relationship.state.hasDematerializedInverse = true;
2277
+ }
2278
+ if (!silenceNotifications) {
2279
+ notifyChange(graph, relationship);
2280
+ }
2281
+ } else {
2282
+ if (!relationship.definition.isAsync || inverseIdentifier && checkIfNew(graph._realStore, inverseIdentifier)) {
2283
+ // unloading inverse of a sync relationship is treated as a client-side
2284
+ // delete, so actually remove the models don't merely invalidate the cp
2285
+ // cache.
2286
+ // if the record being unloaded only exists on the client, we similarly
2287
+ // treat it as a client side delete
2288
+ /*#__NOINLINE__*/
2289
+ removeIdentifierCompletelyFromRelationship(graph, relationship, inverseIdentifier);
2290
+ } else {
2291
+ relationship.state.hasDematerializedInverse = true;
2292
+ }
2293
+ if (!silenceNotifications) {
2294
+ notifyChange(graph, relationship);
2295
+ }
2296
+ }
2297
+ }
2298
+ function removeCompletelyFromInverse(graph, relationship) {
2299
+ const {
2300
+ identifier
2301
+ } = relationship;
2302
+ const {
2303
+ inverseKey
2304
+ } = relationship.definition;
2305
+ forAllRelatedIdentifiers(relationship, inverseIdentifier => {
2306
+ if (graph.has(inverseIdentifier, inverseKey)) {
2307
+ removeIdentifierCompletelyFromRelationship(graph, graph.get(inverseIdentifier, inverseKey), identifier);
2308
+ }
2309
+ });
2310
+ if (isBelongsTo(relationship)) {
2311
+ if (!relationship.definition.isAsync) {
2312
+ clearRelationship(relationship);
2313
+ }
2314
+ relationship.localState = null;
2315
+ } else if (isHasMany(relationship)) {
2316
+ if (!relationship.definition.isAsync) {
2317
+ clearRelationship(relationship);
2318
+ notifyChange(graph, relationship);
2319
+ }
2320
+ } else {
2321
+ relationship.remoteMembers.clear();
2322
+ relationship.localMembers.clear();
2323
+ }
2324
+ }
2325
+ function addPending(cache, definition, op) {
2326
+ const cacheForKind = cache[definition.kind] = cache[definition.kind] || new Map();
2327
+ let cacheForType = cacheForKind.get(definition.inverseType);
2328
+ if (!cacheForType) {
2329
+ cacheForType = new Map();
2330
+ cacheForKind.set(definition.inverseType, cacheForType);
2331
+ }
2332
+ let cacheForField = cacheForType.get(op.field);
2333
+ if (!cacheForField) {
2334
+ cacheForField = [];
2335
+ cacheForType.set(op.field, cacheForField);
2336
+ }
2337
+ cacheForField.push(op);
2338
+ }
2339
+ function isReordered(relationship) {
2340
+ // if we are dirty we are never re-ordered because accessing
2341
+ // the state would flush away any reordering.
2342
+ if (relationship.isDirty) {
2343
+ return false;
2344
+ }
2345
+ const {
2346
+ remoteState,
2347
+ localState,
2348
+ additions,
2349
+ removals
2350
+ } = relationship;
2351
+ if (localState === null) {
2352
+ // the relationship has never been accessed, so it hasn't been reordered either
2353
+ return false;
2354
+ }
2355
+ for (let i = 0, j = 0; i < remoteState.length; i++) {
2356
+ const member = remoteState[i];
2357
+ const localMember = localState[j];
2358
+ if (member !== localMember) {
2359
+ if (removals && removals.has(member)) {
2360
+ // dont increment j because we want to skip this
2361
+ continue;
2362
+ }
2363
+ if (additions && additions.has(localMember)) {
2364
+ // increment j to skip this localMember
2365
+ // decrement i to repeat this remoteMember
2366
+ j++;
2367
+ i--;
2368
+ continue;
2369
+ }
2370
+ return true;
2371
+ }
2372
+
2373
+ // if we made it here, increment j
2374
+ j++;
2375
+ }
2376
+ return false;
2377
+ }
2378
+
2379
+ /**
2380
+ Provides a performance tuned normalized graph for intelligently managing relationships between resources based on identity
2381
+
2382
+ While this Graph is abstract, it currently is a private implementation required as a peer-dependency by the {json:api} Cache Implementation.
2383
+
2384
+ We intend to make this Graph public API after some additional iteration during the 5.x timeframe, until then all APIs should be considered experimental and unstable, not fit for direct application or 3rd party library usage.
2385
+
2386
+ @module
2387
+ */
2388
+
2389
+ function isStore(maybeStore) {
2390
+ return maybeStore._instanceCache !== undefined;
2391
+ }
2392
+ function getWrapper(store) {
2393
+ return isStore(store) ? store._instanceCache._storeWrapper : store;
2394
+ }
2395
+ function peekGraph(store) {
2396
+ return Graphs.get(getWrapper(store));
2397
+ }
2398
+ function graphFor(store) {
2399
+ const wrapper = getWrapper(store);
2400
+ let graph = Graphs.get(wrapper);
2401
+ if (!graph) {
2402
+ graph = new Graph(wrapper);
2403
+ Graphs.set(wrapper, graph);
2404
+ getStore(wrapper)._graph = graph;
2405
+ }
2406
+ return graph;
2407
+ }
2408
+ export { graphFor, isBelongsTo, peekGraph };