teamplay 0.4.0 → 0.5.0-alpha.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 (237) hide show
  1. package/babel-loader.cjs +1 -0
  2. package/babel.cjs +1 -0
  3. package/dist/connect/index.d.ts +1 -0
  4. package/dist/connect/index.js +9 -0
  5. package/dist/connect/lib/sharedb-crosstab-pubsub.d.cts +10 -0
  6. package/dist/connect/offline/index.d.ts +1 -0
  7. package/dist/connect/offline/index.js +126 -0
  8. package/dist/connect/offline/react-native.d.ts +10 -0
  9. package/dist/connect/offline/react-native.js +25 -0
  10. package/dist/connect/offline/web.d.ts +9 -0
  11. package/dist/connect/offline/web.js +12 -0
  12. package/dist/connect/sharedbConnection.d.cts +2 -0
  13. package/dist/connect/test.d.ts +1 -0
  14. package/dist/connect/test.js +12 -0
  15. package/dist/index.d.ts +66 -0
  16. package/dist/index.js +74 -0
  17. package/dist/orm/$.d.ts +9 -0
  18. package/dist/orm/$.js +34 -0
  19. package/dist/orm/Aggregation.d.ts +17 -0
  20. package/dist/orm/Aggregation.js +115 -0
  21. package/dist/orm/Cache.d.ts +9 -0
  22. package/dist/orm/Cache.js +32 -0
  23. package/dist/orm/Compat/SignalCompat.d.ts +3 -0
  24. package/dist/orm/Compat/SignalCompat.js +1542 -0
  25. package/dist/orm/Compat/eventsCompat.d.ts +3 -0
  26. package/dist/orm/Compat/eventsCompat.js +73 -0
  27. package/dist/orm/Compat/hooksCompat.d.ts +33 -0
  28. package/dist/orm/Compat/hooksCompat.js +360 -0
  29. package/dist/orm/Compat/modelEvents.d.ts +6 -0
  30. package/dist/orm/Compat/modelEvents.js +228 -0
  31. package/dist/orm/Compat/queryReadiness.d.ts +5 -0
  32. package/dist/orm/Compat/queryReadiness.js +185 -0
  33. package/dist/orm/Compat/refFallback.d.ts +13 -0
  34. package/dist/orm/Compat/refFallback.js +65 -0
  35. package/dist/orm/Compat/refRegistry.d.ts +6 -0
  36. package/dist/orm/Compat/refRegistry.js +54 -0
  37. package/dist/orm/Compat/silentContext.d.ts +5 -0
  38. package/dist/orm/Compat/silentContext.js +48 -0
  39. package/dist/orm/Compat/startStopCompat.d.ts +3 -0
  40. package/dist/orm/Compat/startStopCompat.js +217 -0
  41. package/dist/orm/Doc.d.ts +96 -0
  42. package/dist/orm/Doc.js +966 -0
  43. package/dist/orm/Query.d.ts +117 -0
  44. package/dist/orm/Query.js +1111 -0
  45. package/dist/orm/Reaction.d.ts +10 -0
  46. package/dist/orm/Reaction.js +41 -0
  47. package/dist/orm/Root.d.ts +22 -0
  48. package/dist/orm/Root.js +85 -0
  49. package/dist/orm/Signal.d.ts +12 -0
  50. package/dist/orm/Signal.js +7 -0
  51. package/dist/orm/SignalBase.d.ts +168 -0
  52. package/dist/orm/SignalBase.js +625 -0
  53. package/dist/orm/SubscriptionState.d.ts +17 -0
  54. package/dist/orm/SubscriptionState.js +123 -0
  55. package/dist/orm/Value.d.ts +9 -0
  56. package/dist/orm/Value.js +25 -0
  57. package/dist/orm/addModel.d.ts +6 -0
  58. package/dist/orm/addModel.js +39 -0
  59. package/dist/orm/associations.d.ts +18 -0
  60. package/dist/orm/associations.js +70 -0
  61. package/dist/orm/batchScheduler.d.ts +7 -0
  62. package/dist/orm/batchScheduler.js +62 -0
  63. package/dist/orm/compatEnv.d.ts +1 -0
  64. package/dist/orm/compatEnv.js +4 -0
  65. package/dist/orm/connection.d.ts +26 -0
  66. package/dist/orm/connection.js +38 -0
  67. package/dist/orm/dataTree.d.ts +34 -0
  68. package/dist/orm/dataTree.js +880 -0
  69. package/dist/orm/disposeRootContext.d.ts +4 -0
  70. package/dist/orm/disposeRootContext.js +59 -0
  71. package/dist/orm/getSignal.d.ts +16 -0
  72. package/dist/orm/getSignal.js +133 -0
  73. package/dist/orm/idFields.d.ts +14 -0
  74. package/dist/orm/idFields.js +95 -0
  75. package/dist/orm/index.d.ts +7 -0
  76. package/dist/orm/index.js +6 -0
  77. package/dist/orm/initModels.d.ts +5 -0
  78. package/dist/orm/initModels.js +74 -0
  79. package/dist/orm/missingDoc.d.ts +1 -0
  80. package/dist/orm/missingDoc.js +3 -0
  81. package/dist/orm/pluralize.d.ts +12 -0
  82. package/dist/orm/privateData.d.ts +22 -0
  83. package/dist/orm/privateData.js +170 -0
  84. package/dist/orm/rootContext.d.ts +78 -0
  85. package/dist/orm/rootContext.js +297 -0
  86. package/dist/orm/rootScope.d.ts +12 -0
  87. package/dist/orm/rootScope.js +46 -0
  88. package/dist/orm/signalArrayReaders.d.ts +22 -0
  89. package/dist/orm/signalArrayReaders.js +42 -0
  90. package/dist/orm/signalMetadata.d.ts +17 -0
  91. package/dist/orm/signalMetadata.js +56 -0
  92. package/dist/orm/signalMutationGuards.d.ts +4 -0
  93. package/dist/orm/signalMutationGuards.js +14 -0
  94. package/dist/orm/signalPathKind.d.ts +2 -0
  95. package/dist/orm/signalPathKind.js +10 -0
  96. package/dist/orm/signalPathRules.d.ts +6 -0
  97. package/dist/orm/signalPathRules.js +28 -0
  98. package/dist/orm/signalReads.d.ts +26 -0
  99. package/dist/orm/signalReads.js +64 -0
  100. package/dist/orm/signalRuntimeAccess.d.ts +16 -0
  101. package/dist/orm/signalRuntimeAccess.js +24 -0
  102. package/dist/orm/signalRuntimeDescriptor.d.ts +19 -0
  103. package/dist/orm/signalRuntimeDescriptor.js +97 -0
  104. package/dist/orm/signalStorageMutations.d.ts +18 -0
  105. package/dist/orm/signalStorageMutations.js +26 -0
  106. package/dist/orm/signalSymbols.d.ts +5 -0
  107. package/dist/orm/signalSymbols.js +5 -0
  108. package/dist/orm/signalValueMutations.d.ts +14 -0
  109. package/dist/orm/signalValueMutations.js +36 -0
  110. package/dist/orm/sub.d.ts +37 -0
  111. package/dist/orm/sub.js +187 -0
  112. package/dist/orm/subscriptionGcDelay.d.ts +4 -0
  113. package/dist/orm/subscriptionGcDelay.js +26 -0
  114. package/dist/orm/types/baseMethods.d.ts +43 -0
  115. package/dist/orm/types/baseMethods.js +1 -0
  116. package/dist/orm/types/jsonSchema.d.ts +75 -0
  117. package/dist/orm/types/jsonSchema.js +1 -0
  118. package/dist/orm/types/modelManifest.d.ts +37 -0
  119. package/dist/orm/types/modelManifest.js +1 -0
  120. package/dist/orm/types/path.d.ts +8 -0
  121. package/dist/orm/types/path.js +1 -0
  122. package/dist/orm/types/query.d.ts +29 -0
  123. package/dist/orm/types/query.js +1 -0
  124. package/dist/orm/types/signal.d.ts +132 -0
  125. package/dist/orm/types/signal.js +1 -0
  126. package/dist/react/compatComponentRegistry.d.ts +4 -0
  127. package/dist/react/compatComponentRegistry.js +19 -0
  128. package/dist/react/convertToObserver.d.ts +26 -0
  129. package/dist/react/convertToObserver.js +114 -0
  130. package/dist/react/executionContextTracker.d.ts +11 -0
  131. package/dist/react/executionContextTracker.js +28 -0
  132. package/dist/react/helpers.d.ts +34 -0
  133. package/dist/react/helpers.js +134 -0
  134. package/dist/react/observer.d.ts +22 -0
  135. package/dist/react/observer.js +8 -0
  136. package/dist/react/promiseBatcher.d.ts +13 -0
  137. package/dist/react/promiseBatcher.js +118 -0
  138. package/dist/react/renderAttemptDestroyer.d.ts +19 -0
  139. package/dist/react/renderAttemptDestroyer.js +46 -0
  140. package/dist/react/trapRender.d.ts +6 -0
  141. package/dist/react/trapRender.js +48 -0
  142. package/dist/react/universal$.d.ts +1 -0
  143. package/dist/react/universal$.js +18 -0
  144. package/dist/react/universalSub.d.ts +1 -0
  145. package/dist/react/universalSub.js +22 -0
  146. package/dist/react/useApi.d.ts +13 -0
  147. package/dist/react/useApi.js +67 -0
  148. package/dist/react/useSub.d.ts +102 -0
  149. package/dist/react/useSub.js +189 -0
  150. package/dist/react/useSuspendMemo.d.ts +3 -0
  151. package/dist/react/useSuspendMemo.js +102 -0
  152. package/dist/react/wrapIntoSuspense.d.ts +9 -0
  153. package/dist/react/wrapIntoSuspense.js +102 -0
  154. package/dist/server.d.ts +11 -0
  155. package/dist/server.js +28 -0
  156. package/dist/utils/MockFinalizationRegistry.d.ts +37 -0
  157. package/dist/utils/MockFinalizationRegistry.js +85 -0
  158. package/dist/utils/MockWeakRef.d.ts +12 -0
  159. package/dist/utils/MockWeakRef.js +23 -0
  160. package/dist/utils/isServer.d.ts +3 -0
  161. package/dist/utils/isServer.js +18 -0
  162. package/dist/utils/setDiffDeep.d.ts +1 -0
  163. package/dist/utils/setDiffDeep.js +61 -0
  164. package/dist/utils/useIsomorphicLayoutEffect.d.ts +3 -0
  165. package/dist/utils/useIsomorphicLayoutEffect.js +3 -0
  166. package/file-based-models.js +3 -0
  167. package/file-based-models.node.js +6 -0
  168. package/package.json +77 -32
  169. package/teamplay.models.auto-init.virtual.js +1 -0
  170. package/teamplay.models.virtual.js +1 -0
  171. package/connect/index.js +0 -9
  172. package/connect/offline/index.js +0 -124
  173. package/connect/offline/react-native.js +0 -28
  174. package/connect/offline/web.js +0 -15
  175. package/connect/test.js +0 -12
  176. package/index.d.ts +0 -106
  177. package/index.js +0 -125
  178. package/orm/$.js +0 -38
  179. package/orm/Aggregation.js +0 -117
  180. package/orm/Cache.js +0 -46
  181. package/orm/Compat/README.md +0 -1064
  182. package/orm/Compat/SignalCompat.js +0 -1479
  183. package/orm/Compat/eventsCompat.js +0 -79
  184. package/orm/Compat/hooksCompat.js +0 -374
  185. package/orm/Compat/modelEvents.js +0 -225
  186. package/orm/Compat/queryReadiness.js +0 -191
  187. package/orm/Compat/refFallback.js +0 -62
  188. package/orm/Compat/refRegistry.js +0 -61
  189. package/orm/Compat/silentContext.js +0 -51
  190. package/orm/Compat/startStopCompat.js +0 -207
  191. package/orm/Doc.js +0 -969
  192. package/orm/Query.js +0 -1127
  193. package/orm/Reaction.js +0 -48
  194. package/orm/Root.js +0 -88
  195. package/orm/Signal.js +0 -22
  196. package/orm/SignalBase.js +0 -696
  197. package/orm/SubscriptionState.js +0 -138
  198. package/orm/Value.js +0 -30
  199. package/orm/addModel.js +0 -31
  200. package/orm/associations.js +0 -97
  201. package/orm/batchScheduler.js +0 -62
  202. package/orm/compatEnv.js +0 -4
  203. package/orm/connection.js +0 -46
  204. package/orm/dataTree.js +0 -869
  205. package/orm/disposeRootContext.js +0 -68
  206. package/orm/getSignal.js +0 -130
  207. package/orm/idFields.js +0 -88
  208. package/orm/index.d.ts +0 -6
  209. package/orm/index.js +0 -5
  210. package/orm/missingDoc.js +0 -3
  211. package/orm/privateData.js +0 -181
  212. package/orm/rootContext.js +0 -313
  213. package/orm/rootScope.js +0 -51
  214. package/orm/sub.js +0 -151
  215. package/orm/subscriptionGcDelay.js +0 -32
  216. package/react/compatComponentRegistry.js +0 -20
  217. package/react/convertToObserver.js +0 -117
  218. package/react/executionContextTracker.js +0 -32
  219. package/react/helpers.js +0 -141
  220. package/react/observer.js +0 -9
  221. package/react/promiseBatcher.js +0 -115
  222. package/react/renderAttemptDestroyer.js +0 -47
  223. package/react/trapRender.js +0 -50
  224. package/react/universal$.js +0 -18
  225. package/react/universalSub.js +0 -21
  226. package/react/useApi.js +0 -63
  227. package/react/useSub.js +0 -169
  228. package/react/useSuspendMemo.js +0 -96
  229. package/react/wrapIntoSuspense.js +0 -119
  230. package/server.js +0 -31
  231. package/utils/MockFinalizationRegistry.js +0 -94
  232. package/utils/MockWeakRef.js +0 -25
  233. package/utils/isServer.js +0 -17
  234. package/utils/setDiffDeep.js +0 -58
  235. package/utils/useIsomorphicLayoutEffect.js +0 -4
  236. /package/{connect → dist/connect}/lib/sharedb-crosstab-pubsub.cjs +0 -0
  237. /package/{connect → dist/connect}/sharedbConnection.cjs +0 -0
@@ -1,32 +0,0 @@
1
- export default new class ExecutionContextTracker {
2
- #contextId
3
- #hooksCounter
4
-
5
- isActive () {
6
- return this.#contextId !== undefined
7
- }
8
-
9
- getComponentId () {
10
- return this.#contextId
11
- }
12
-
13
- newHookId () {
14
- this.incrementHooksCounter()
15
- const id = `_${this.#contextId}_${this.#hooksCounter}`
16
- return id
17
- }
18
-
19
- incrementHooksCounter () {
20
- if (!this.#contextId) return
21
- this.#hooksCounter++
22
- }
23
-
24
- _start (contextId) {
25
- this.#contextId = contextId
26
- this.#hooksCounter = -1
27
- }
28
-
29
- _clear () {
30
- this.#contextId = undefined
31
- }
32
- }()
package/react/helpers.js DELETED
@@ -1,141 +0,0 @@
1
- import { useContext, createContext, useRef, useEffect, useLayoutEffect } from 'react'
2
-
3
- export const ComponentMetaContext = createContext({})
4
-
5
- export function pipeComponentDisplayName (SourceComponent, TargetComponent, suffix = '', defaultName = 'StartupjsWrapper') {
6
- const displayName = SourceComponent.displayName || SourceComponent.name
7
-
8
- if (!TargetComponent.displayName) {
9
- TargetComponent.displayName = displayName ? (displayName + suffix) : defaultName
10
- }
11
- }
12
-
13
- export function pipeComponentMeta (SourceComponent, TargetComponent, suffix = '', defaultName = 'StartupjsWrapper') {
14
- pipeComponentDisplayName(SourceComponent, TargetComponent, suffix, defaultName)
15
-
16
- if (!TargetComponent.propTypes && SourceComponent.propTypes) {
17
- TargetComponent.propTypes = SourceComponent.propTypes
18
- }
19
- if (!TargetComponent.defaultProps && SourceComponent.defaultProps) {
20
- TargetComponent.defaultProps = SourceComponent.defaultProps
21
- }
22
- return TargetComponent
23
- }
24
-
25
- export function useNow () {
26
- const context = useContext(ComponentMetaContext)
27
- if (!context) throw Error(ERRORS.useNow)
28
- return context.createdAt
29
- }
30
-
31
- export function useId () {
32
- const context = useContext(ComponentMetaContext)
33
- if (!context) throw Error(ERRORS.useId)
34
- return context.componentId
35
- }
36
-
37
- export function useTriggerUpdate () {
38
- const context = useContext(ComponentMetaContext)
39
- if (!context) throw Error(ERRORS.useTriggerUpdate)
40
- return context.triggerUpdate
41
- }
42
-
43
- export function useScheduleUpdate () {
44
- const context = useContext(ComponentMetaContext)
45
- if (!context) throw Error(ERRORS.useScheduleUpdate)
46
- return context.scheduleUpdate
47
- }
48
-
49
- export function useDefer () {
50
- const context = useContext(ComponentMetaContext)
51
- if (!context) throw Error(ERRORS.useScheduleUpdate)
52
- return context.defer
53
- }
54
-
55
- export function useCache (key) {
56
- const context = useContext(ComponentMetaContext)
57
- if (!context) throw Error(ERRORS.useCache)
58
- return context.cache
59
- }
60
-
61
- export function useUnmount (fn) {
62
- const fnRef = useRef()
63
- if (fnRef.current !== fn) fnRef.current = fn
64
- useEffect(
65
- () => () => {
66
- fnRef.current()
67
- },
68
- []
69
- )
70
- }
71
-
72
- export function useDidUpdate (fn, deps) {
73
- const isFirst = useRef(true)
74
- const fnRef = useRef(fn)
75
- if (fnRef.current !== fn) fnRef.current = fn
76
- const stableDeps = useStableDeps(deps)
77
- useEffect(() => {
78
- if (isFirst.current) {
79
- isFirst.current = false
80
- return
81
- }
82
- return fnRef.current()
83
- }, stableDeps)
84
- }
85
-
86
- export function useOnce (condition, fn) {
87
- const fired = useRef(false)
88
- useEffect(() => {
89
- if (fired.current) return
90
- if (!condition) return
91
- fired.current = true
92
- return fn()
93
- }, [condition, fn])
94
- }
95
-
96
- export function useSyncEffect (fn, deps) {
97
- const stableDeps = useStableDeps(deps)
98
- useLayoutEffect(fn, [fn, stableDeps])
99
- }
100
-
101
- function useStableDeps (deps) {
102
- const depsRef = useRef([])
103
- const nextDeps = Array.isArray(deps) ? deps : []
104
- if (!shallowEqualArrays(depsRef.current, nextDeps)) {
105
- depsRef.current = nextDeps
106
- }
107
- return depsRef.current
108
- }
109
-
110
- function shallowEqualArrays (a, b) {
111
- if (a === b) return true
112
- if (!Array.isArray(a) || !Array.isArray(b)) return false
113
- if (a.length !== b.length) return false
114
- for (let i = 0; i < a.length; i++) {
115
- if (!Object.is(a[i], b[i])) return false
116
- }
117
- return true
118
- }
119
-
120
- const ERRORS = {
121
- useTriggerUpdate: `
122
- useTriggerUpdate() can only be used inside a component wrapped with observer().
123
- You have probably forgot to wrap your component with observer().
124
- `,
125
- useScheduleUpdate: `
126
- useScheduleUpdate() can only be used inside a component wrapped with observer().
127
- You have probably forgot to wrap your component with observer().
128
- `,
129
- useId: `
130
- useId() can only be used inside a component wrapped with observer().
131
- You have probably forgot to wrap your component with observer().
132
- `,
133
- useNow: `
134
- useNow() can only be used inside a component wrapped with observer().
135
- You have probably forgot to wrap your component with observer().
136
- `,
137
- useCache: `
138
- useCache() can only be used inside a component wrapped with observer().
139
- You have probably forgot to wrap your component with observer().
140
- `
141
- }
package/react/observer.js DELETED
@@ -1,9 +0,0 @@
1
- import convertToObserver from './convertToObserver.js'
2
- import wrapIntoSuspense from './wrapIntoSuspense.js'
3
-
4
- function observer (Component, options) {
5
- return wrapIntoSuspense(convertToObserver(Component, options))
6
- }
7
- observer.__wrapObserverMeta = wrapIntoSuspense
8
- observer.__makeObserver = convertToObserver
9
- export default observer
@@ -1,115 +0,0 @@
1
- let active = false
2
- let promises = []
3
- let checks = new Map()
4
-
5
- const READINESS_POLL_INTERVAL_MS = 16
6
- const READINESS_WARN_AFTER_MS = 1000
7
-
8
- export function activate () {
9
- active = true
10
- }
11
-
12
- export function add (promise) {
13
- if (!promise || typeof promise.then !== 'function') return
14
- promises.push(promise)
15
- }
16
-
17
- export function addCheck (check) {
18
- if (!check || typeof check.isReady !== 'function') return
19
- const key = check.key ?? Symbol('batch-check')
20
- checks.set(key, { ...check, key })
21
- }
22
-
23
- export function getPromiseAll () {
24
- const pendingPromises = promises
25
- const pendingChecks = Array.from(checks.values())
26
- const hasPromises = pendingPromises.length > 0
27
- // Checks are a materialization barrier for initial batch subscriptions.
28
- // If there were no subscription promises in this render, we are in update mode
29
- // and should not suspend the whole subtree.
30
- const result = hasPromises
31
- ? waitForBatchReady(pendingPromises, pendingChecks)
32
- : null
33
- reset()
34
- return result
35
- }
36
-
37
- export function isActive () {
38
- return active
39
- }
40
-
41
- export function reset () {
42
- active = false
43
- promises = []
44
- checks = new Map()
45
- }
46
-
47
- async function waitForBatchReady (pendingPromises, pendingChecks) {
48
- if (pendingPromises.length > 0) await Promise.all(pendingPromises)
49
- // Let microtasks flush after subscription promises resolve so tree writes become visible.
50
- await Promise.resolve()
51
- await waitForChecksReady(pendingChecks)
52
- }
53
-
54
- async function waitForChecksReady (pendingChecks) {
55
- if (pendingChecks.length === 0) return
56
- let warned = false
57
- const startedAt = Date.now()
58
- while (true) {
59
- const notReadyChecks = getNotReadyChecks(pendingChecks)
60
- if (notReadyChecks.length === 0) return
61
- if (!warned && isDevMode() && Date.now() - startedAt >= READINESS_WARN_AFTER_MS) {
62
- warned = true
63
- warnAboutChecksDelay(notReadyChecks)
64
- }
65
- await delay(READINESS_POLL_INTERVAL_MS)
66
- }
67
- }
68
-
69
- function getNotReadyChecks (pendingChecks) {
70
- const notReady = []
71
- for (const check of pendingChecks) {
72
- if (!isCheckReady(check)) notReady.push(check)
73
- }
74
- return notReady
75
- }
76
-
77
- function isCheckReady (check) {
78
- try {
79
- return !!check.isReady()
80
- } catch (err) {
81
- if (isThenable(err)) return false
82
- throw err
83
- }
84
- }
85
-
86
- function warnAboutChecksDelay (checks) {
87
- const details = checks.map(check => {
88
- let state
89
- try {
90
- state = typeof check.getState === 'function' ? check.getState() : undefined
91
- } catch (err) {
92
- state = isThenable(err) ? 'suspended' : `state-error: ${err?.message || err}`
93
- }
94
- return {
95
- type: check.type || 'unknown',
96
- key: String(check.key),
97
- details: check.details,
98
- state
99
- }
100
- })
101
- console.warn('[teamplay] useBatch() is waiting for data materialization checks.', details)
102
- }
103
-
104
- function isDevMode () {
105
- if (typeof process === 'undefined' || !process?.env) return true
106
- return process.env.NODE_ENV !== 'production'
107
- }
108
-
109
- function isThenable (value) {
110
- return !!value && typeof value.then === 'function'
111
- }
112
-
113
- function delay (ms) {
114
- return new Promise(resolve => setTimeout(resolve, ms))
115
- }
@@ -1,47 +0,0 @@
1
- class RenderAttemptDestroyer {
2
- constructor () {
3
- this.fns = []
4
- this.compatAttemptCleanupArmed = false
5
- this.suspenseGateArmed = false
6
- }
7
-
8
- add (fn, { compat = false } = {}) {
9
- if (typeof fn !== 'function') return
10
- this.fns.push(fn)
11
- if (compat) this.compatAttemptCleanupArmed = true
12
- }
13
-
14
- armCompatAttemptCleanup () {
15
- this.compatAttemptCleanupArmed = true
16
- }
17
-
18
- armSuspenseGate () {
19
- this.suspenseGateArmed = true
20
- }
21
-
22
- consumeThenableHandling () {
23
- const shouldRunAttemptCleanup = this.compatAttemptCleanupArmed && this.fns.length > 0
24
- const shouldKeepShellAlive = this.suspenseGateArmed || shouldRunAttemptCleanup
25
- let destroyAttempt
26
- if (shouldRunAttemptCleanup) {
27
- const fns = [...this.fns]
28
- destroyAttempt = async () => {
29
- await Promise.allSettled(fns.map(fn => fn()))
30
- fns.length = 0
31
- }
32
- }
33
- this.reset()
34
- return {
35
- shouldKeepShellAlive,
36
- destroyAttempt
37
- }
38
- }
39
-
40
- reset () {
41
- this.fns.length = 0
42
- this.compatAttemptCleanupArmed = false
43
- this.suspenseGateArmed = false
44
- }
45
- }
46
-
47
- export default new RenderAttemptDestroyer()
@@ -1,50 +0,0 @@
1
- // trap render function (functional component) to block observer updates and activate cache
2
- // during synchronous rendering
3
- import executionContextTracker from './executionContextTracker.js'
4
- import * as promiseBatcher from './promiseBatcher.js'
5
- import renderAttemptDestroyer from './renderAttemptDestroyer.js'
6
-
7
- export default function trapRender ({ render, cache, destroy, componentId }) {
8
- return (...args) => {
9
- executionContextTracker._start(componentId)
10
- cache.activate()
11
- let destroyed
12
- try {
13
- renderAttemptDestroyer.reset()
14
- promiseBatcher.reset()
15
- const res = render(...args)
16
- if (isDevMode() && promiseBatcher.isActive()) {
17
- throw Error('[teamplay] useBatch* hooks were used without a closing useBatch() call.')
18
- }
19
- return res
20
- } catch (err) {
21
- promiseBatcher.reset()
22
- if (!err.then) {
23
- destroy('trapRender.js')
24
- destroyed = true
25
- throw err
26
- }
27
- const {
28
- shouldKeepShellAlive,
29
- destroyAttempt
30
- } = renderAttemptDestroyer.consumeThenableHandling()
31
- if (shouldKeepShellAlive) {
32
- throw Promise.resolve(err).then(() => destroyAttempt?.())
33
- }
34
-
35
- // TODO: this might only be needed only if promise is thrown
36
- // (check if useUnmount in convertToObserver is called if a regular error is thrown)
37
- destroy('trapRender.js')
38
- destroyed = true
39
- throw err
40
- } finally {
41
- if (!destroyed) cache.deactivate()
42
- executionContextTracker._clear()
43
- }
44
- }
45
- }
46
-
47
- function isDevMode () {
48
- if (typeof process === 'undefined' || !process?.env) return true
49
- return process.env.NODE_ENV !== 'production'
50
- }
@@ -1,18 +0,0 @@
1
- import $ from '../orm/$.js'
2
- import { useCache } from './helpers.js'
3
- import executionContextTracker from './executionContextTracker.js'
4
-
5
- // universal versions of $() which work as a plain function or as a react hook
6
- export default function universal$ ($root, value) {
7
- if (executionContextTracker.isActive()) {
8
- // within react component
9
- const id = executionContextTracker.newHookId()
10
- const cache = useCache() // eslint-disable-line react-hooks/rules-of-hooks
11
- const $signal = $($root, value, id)
12
- cache.set(id, $signal)
13
- // save signal into ref to make sure it's not garbage collected while component exists
14
- return $signal
15
- } else {
16
- return $($root, value)
17
- }
18
- }
@@ -1,21 +0,0 @@
1
- // NOTE: this is not used currently since using an explicit useSub()
2
- // hook is easier to understand in a React context.
3
- // Having the same sub() function working with either await or without it
4
- // is confusing. It's better to have a separate function for the hook.
5
- import { useRef } from 'react'
6
- import sub from '../orm/sub.js'
7
- import executionContextTracker from './executionContextTracker.js'
8
-
9
- // universal versions of sub() which work as a plain function or as a react hook
10
- export default function universalSub (...args) {
11
- const promiseOrSignal = sub(...args)
12
- if (executionContextTracker.isActive()) {
13
- // within react component
14
- // 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
15
- if (promiseOrSignal.then) throw promiseOrSignal
16
- // 2. if it's a signal, we save it into ref to make sure it's not garbage collected while component exists
17
- const $signalRef = useRef() // eslint-disable-line react-hooks/rules-of-hooks
18
- if ($signalRef.current !== promiseOrSignal) $signalRef.current = promiseOrSignal
19
- }
20
- return promiseOrSignal
21
- }
package/react/useApi.js DELETED
@@ -1,63 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react'
2
-
3
- export default function useApi (api, args = [], options = {}) {
4
- const { debounce = 0 } = options || {}
5
- const [data, setData] = useState()
6
- const [error, setError] = useState()
7
- const [loading, setLoading] = useState(false)
8
- const requestIdRef = useRef(0)
9
- const stableArgs = useStableDeps(Array.isArray(args) ? args : [args])
10
-
11
- useEffect(() => {
12
- if (typeof api !== 'function') return
13
- let cancelled = false
14
- const requestId = ++requestIdRef.current
15
- let timer
16
-
17
- const run = async () => {
18
- try {
19
- setLoading(true)
20
- const result = await api(...stableArgs)
21
- if (cancelled || requestId !== requestIdRef.current) return
22
- setData(result)
23
- setError(undefined)
24
- } catch (err) {
25
- if (cancelled || requestId !== requestIdRef.current) return
26
- setError(err)
27
- } finally {
28
- if (!cancelled && requestId === requestIdRef.current) setLoading(false)
29
- }
30
- }
31
-
32
- if (debounce > 0) {
33
- timer = setTimeout(run, debounce)
34
- } else {
35
- run()
36
- }
37
-
38
- return () => {
39
- cancelled = true
40
- if (timer) clearTimeout(timer)
41
- }
42
- }, [api, debounce, stableArgs])
43
-
44
- return [data, loading, error]
45
- }
46
-
47
- function useStableDeps (deps) {
48
- const depsRef = useRef([])
49
- if (!shallowEqualArrays(depsRef.current, deps)) {
50
- depsRef.current = deps
51
- }
52
- return depsRef.current
53
- }
54
-
55
- function shallowEqualArrays (a, b) {
56
- if (a === b) return true
57
- if (!Array.isArray(a) || !Array.isArray(b)) return false
58
- if (a.length !== b.length) return false
59
- for (let i = 0; i < a.length; i++) {
60
- if (!Object.is(a[i], b[i])) return false
61
- }
62
- return true
63
- }
package/react/useSub.js DELETED
@@ -1,169 +0,0 @@
1
- import { useRef, useDeferredValue } from 'react'
2
- import sub from '../orm/sub.js'
3
- import { useScheduleUpdate, useCache, useDefer, useId } from './helpers.js'
4
- import executionContextTracker from './executionContextTracker.js'
5
- import * as promiseBatcher from './promiseBatcher.js'
6
- import renderAttemptDestroyer from './renderAttemptDestroyer.js'
7
- import { markCompatComponent } from './compatComponentRegistry.js'
8
-
9
- let TEST_THROTTLING = false
10
-
11
- // experimental feature to leverage useDeferredValue() to handle re-subscriptions.
12
- // Currently it does lead to issues with extra rerenders and requires further investigation
13
- let USE_DEFERRED_VALUE = true
14
- // by default we want to defer stuff if possible instead of throwing promises
15
- let DEFAULT_DEFER = true
16
-
17
- export function useAsyncSub (signal, params, options) {
18
- return useSub(signal, params, { ...options, async: true })
19
- }
20
-
21
- export default function useSub (signal, params, options) {
22
- if (USE_DEFERRED_VALUE) {
23
- return useSubDeferred(signal, params, options) // eslint-disable-line react-hooks/rules-of-hooks
24
- } else {
25
- return useSubClassic(signal, params, options) // eslint-disable-line react-hooks/rules-of-hooks
26
- }
27
- }
28
-
29
- // version of sub() which works as a react hook and throws promise for Suspense
30
- export function useSubDeferred (signal, params, { async = false, defer, batch = false, compatAttemptCleanup = false } = {}) {
31
- const $signalRef = useRef() // eslint-disable-line react-hooks/rules-of-hooks
32
- const componentId = useId()
33
- const scheduleUpdate = useScheduleUpdate()
34
- const observerDefer = useDefer()
35
- if (compatAttemptCleanup) markCompatComponent(componentId)
36
- if (batch) promiseBatcher.activate()
37
- defer ??= observerDefer ?? DEFAULT_DEFER
38
- if (defer) {
39
- signal = useDeferredValue(signal) // eslint-disable-line react-hooks/rules-of-hooks
40
- params = useDeferredValue(params ? JSON.stringify(params) : undefined) // eslint-disable-line react-hooks/rules-of-hooks
41
- params = params != null ? JSON.parse(params) : undefined
42
- }
43
- const promiseOrSignal = params != null ? sub(signal, params) : sub(signal)
44
- // 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
45
- if (promiseOrSignal.then) {
46
- const promise = maybeThrottle(promiseOrSignal)
47
- const hasPreviousSignal = !!$signalRef.current
48
- if (batch) {
49
- // Batch suspense must block only on initial load.
50
- // On resubscribe we keep rendering previous signal and refresh in background.
51
- if (!hasPreviousSignal) {
52
- promiseBatcher.add(promise)
53
- if (compatAttemptCleanup) registerCompatAttemptCleanup(signal, params)
54
- } else {
55
- scheduleUpdate(promise)
56
- }
57
- if (async) scheduleUpdate(promise)
58
- return $signalRef.current
59
- }
60
- if (async) {
61
- scheduleUpdate(promise)
62
- return
63
- }
64
- // Keep previous snapshot during update re-subscribe and refresh in background.
65
- if (hasPreviousSignal) {
66
- scheduleUpdate(promise)
67
- return $signalRef.current
68
- }
69
- if (compatAttemptCleanup) registerCompatAttemptCleanup(signal, params)
70
- throw promise
71
- // 2. if it's a signal, we save it into ref to make sure it's not garbage collected while component exists
72
- } else {
73
- const $signal = promiseOrSignal
74
- if ($signalRef.current !== $signal) $signalRef.current = $signal
75
- return $signal
76
- }
77
- }
78
-
79
- // classic version which initially throws promise for Suspense
80
- // but if we get a promise second time, we return the last signal and wait for promise to resolve
81
- export function useSubClassic (signal, params, { async = false, batch = false, compatAttemptCleanup = false } = {}) {
82
- const id = executionContextTracker.newHookId()
83
- const componentId = useId()
84
- const cache = useCache()
85
- const activePromiseRef = useRef()
86
- const scheduleUpdate = useScheduleUpdate()
87
- if (compatAttemptCleanup) markCompatComponent(componentId)
88
- if (batch) promiseBatcher.activate()
89
- const promiseOrSignal = params != null ? sub(signal, params) : sub(signal)
90
- // 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
91
- if (promiseOrSignal.then) {
92
- const promise = maybeThrottle(promiseOrSignal)
93
- if (batch) {
94
- const hasPreviousSignal = cache.has(id)
95
- // Batch suspense must block only on initial load.
96
- // On resubscribe we keep rendering previous signal and refresh in background.
97
- if (!hasPreviousSignal) {
98
- promiseBatcher.add(promise)
99
- if (compatAttemptCleanup) registerCompatAttemptCleanup(signal, params)
100
- } else {
101
- scheduleUpdate(promise)
102
- }
103
- if (async) scheduleUpdate(promise)
104
- if (hasPreviousSignal) return cache.get(id)
105
- return
106
- }
107
- // first time we just throw the promise to be caught by Suspense
108
- if (!cache.has(id)) {
109
- // if we are in async mode, we just return nothing and let the user
110
- // handle appearance of signal on their own.
111
- // We manually schedule an update when promise resolves since we can't
112
- // rely on Suspense in this case to automatically trigger component's re-render
113
- if (async) {
114
- scheduleUpdate(promise)
115
- return
116
- }
117
- if (compatAttemptCleanup) registerCompatAttemptCleanup(signal, params)
118
- // in regular mode we throw the promise to be caught by Suspense
119
- // this way we guarantee that the signal with all the data
120
- // will always be there when component is rendered
121
- throw promise
122
- }
123
- // if we already have a previous signal, we return it and wait for new promise to resolve
124
- scheduleUpdate(promise)
125
- return cache.get(id)
126
- // 2. if it's a signal, we save it into ref to make sure it's not garbage collected while component exists
127
- } else {
128
- const $signal = promiseOrSignal
129
- if (cache.get(id) !== $signal) {
130
- activePromiseRef.current = undefined
131
- cache.set(id, $signal)
132
- }
133
- return $signal
134
- }
135
- }
136
-
137
- export function setTestThrottling (ms) {
138
- if (typeof ms !== 'number') throw Error('setTestThrottling() accepts only a number in ms')
139
- if (ms === 0) throw Error('setTestThrottling(0) is not allowed, use resetTestThrottling() instead')
140
- if (ms < 0) throw Error('setTestThrottling() accepts only a positive number in ms')
141
- TEST_THROTTLING = ms
142
- }
143
- export function resetTestThrottling () {
144
- TEST_THROTTLING = false
145
- }
146
- export function setUseDeferredValue (value) {
147
- USE_DEFERRED_VALUE = value
148
- }
149
- export function setDefaultDefer (value) {
150
- DEFAULT_DEFER = value
151
- }
152
-
153
- // throttle to simulate slow network
154
- function maybeThrottle (promise) {
155
- if (!TEST_THROTTLING) return promise
156
- return new Promise((resolve, reject) => {
157
- setTimeout(() => {
158
- promise.then(resolve, reject)
159
- }, TEST_THROTTLING)
160
- })
161
- }
162
-
163
- function registerCompatAttemptCleanup (signal, params) {
164
- // Compat hooks don't build per-hook init objects like Racer.
165
- // We still need a marker so trapRender can defer observer-shell cleanup
166
- // only when a real attempt cleanup exists.
167
- // This path must not arm suspense-gate keep-alive by itself.
168
- renderAttemptDestroyer.armCompatAttemptCleanup()
169
- }