teamplay 0.4.0-alpha.10 → 0.4.0-alpha.100
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.
- package/README.md +73 -0
- package/index.d.ts +4 -0
- package/index.js +14 -1
- package/orm/Aggregation.js +48 -13
- package/orm/Compat/README.md +301 -24
- package/orm/Compat/SignalCompat.js +720 -150
- package/orm/Compat/eventsCompat.js +4 -3
- package/orm/Compat/hooksCompat.js +123 -27
- package/orm/Compat/modelEvents.js +111 -32
- package/orm/Compat/queryReadiness.js +191 -0
- package/orm/Compat/refFallback.js +62 -0
- package/orm/Compat/refRegistry.js +44 -10
- package/orm/Compat/silentContext.js +51 -0
- package/orm/Compat/startStopCompat.js +207 -0
- package/orm/Doc.js +805 -61
- package/orm/Query.js +944 -119
- package/orm/Reaction.js +12 -9
- package/orm/Root.js +47 -0
- package/orm/SignalBase.js +173 -67
- package/orm/Value.js +7 -5
- package/orm/associations.js +97 -0
- package/orm/batchScheduler.js +62 -0
- package/orm/connection.js +17 -2
- package/orm/dataTree.js +394 -99
- package/orm/disposeRootContext.js +68 -0
- package/orm/getSignal.js +37 -20
- package/orm/idFields.js +33 -2
- package/orm/index.d.ts +6 -0
- package/orm/index.js +5 -0
- package/orm/missingDoc.js +3 -0
- package/orm/privateData.js +181 -0
- package/orm/rootContext.js +313 -0
- package/orm/rootScope.js +51 -0
- package/orm/sub.js +15 -6
- package/orm/subscriptionGcDelay.js +32 -0
- package/package.json +4 -2
- package/react/compatComponentRegistry.js +20 -0
- package/react/convertToObserver.js +19 -2
- package/react/helpers.js +4 -2
- package/react/promiseBatcher.js +115 -0
- package/react/renderAttemptDestroyer.js +47 -0
- package/react/trapRender.js +26 -15
- package/react/useSub.js +54 -3
- package/react/useSuspendMemo.js +96 -0
- package/react/wrapIntoSuspense.js +1 -1
- package/server.js +2 -2
- package/utils/setDiffDeep.js +32 -12
package/README.md
CHANGED
|
@@ -19,6 +19,79 @@ Features:
|
|
|
19
19
|
|
|
20
20
|
For installation and documentation see [teamplay.dev](https://teamplay.dev)
|
|
21
21
|
|
|
22
|
+
## ORM Compat Helpers
|
|
23
|
+
|
|
24
|
+
For legacy Racer-style model mixins (for example versioning libraries which call
|
|
25
|
+
`getAssociations()`), use ORM compat helpers from the `teamplay/orm` subpath:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import BaseModel, { hasMany, hasOne, belongsTo } from 'teamplay/orm'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
These helpers attach class-level associations and expose them through
|
|
32
|
+
`$doc.getAssociations()` on model signals.
|
|
33
|
+
|
|
34
|
+
## React Suspense Gates
|
|
35
|
+
|
|
36
|
+
If you need to throw a thenable from render, prefer `useSuspendMemo()` or
|
|
37
|
+
`useSuspendMemoByKey()` over `useMemo()`.
|
|
38
|
+
|
|
39
|
+
Why:
|
|
40
|
+
|
|
41
|
+
- React may restart a suspended initial render.
|
|
42
|
+
- `useMemo()` is not a semantic "run this suspend gate once" primitive.
|
|
43
|
+
- Side-effectful async work like `join()` may accidentally start again on retry.
|
|
44
|
+
|
|
45
|
+
### `useSuspendMemo(factory, deps)`
|
|
46
|
+
|
|
47
|
+
Use it when the suspend gate is local to one observer component instance.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import { observer, useSuspendMemo } from 'teamplay'
|
|
51
|
+
|
|
52
|
+
const Component = observer(({ $stage, userId, stageUserStore }) => {
|
|
53
|
+
useSuspendMemo(() => {
|
|
54
|
+
if (!stageUserStore?.startedAt) {
|
|
55
|
+
throw $stage.join(userId)
|
|
56
|
+
}
|
|
57
|
+
}, [$stage.getId()])
|
|
58
|
+
|
|
59
|
+
return <span>Ready</span>
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This keeps the same pending thenable for the same hook slot while the component
|
|
64
|
+
instance is alive.
|
|
65
|
+
|
|
66
|
+
### `useSuspendMemoByKey(key, factory, deps)`
|
|
67
|
+
|
|
68
|
+
Use it when the async operation must be deduped by business meaning, not just
|
|
69
|
+
by component instance.
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { observer, useSuspendMemoByKey } from 'teamplay'
|
|
73
|
+
|
|
74
|
+
const Component = observer(({ $stage, stageId, userId, stageUserStore }) => {
|
|
75
|
+
useSuspendMemoByKey(
|
|
76
|
+
`stage.join:${stageId}:${userId}`,
|
|
77
|
+
() => {
|
|
78
|
+
if (!stageUserStore?.startedAt) {
|
|
79
|
+
throw $stage.join(userId)
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
[stageId, userId, !!stageUserStore?.startedAt]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return <span>Ready</span>
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This is the right choice when:
|
|
90
|
+
|
|
91
|
+
- the component may remount while the promise is still pending;
|
|
92
|
+
- two different components may trigger the same async operation;
|
|
93
|
+
- the operation should behave like a single in-flight business task.
|
|
94
|
+
|
|
22
95
|
## License
|
|
23
96
|
|
|
24
97
|
MIT
|
package/index.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export {
|
|
|
42
42
|
setUseDeferredValue as __setUseDeferredValue,
|
|
43
43
|
setDefaultDefer as __setDefaultDefer
|
|
44
44
|
} from './react/useSub.js'
|
|
45
|
+
export function useSuspendMemo<T = any> (factory: () => T, deps?: any[]): T
|
|
46
|
+
export function useSuspendMemoByKey<T = any> (key: any, factory: () => T, deps?: any[]): T
|
|
45
47
|
export function useValue (defaultValue?: any): [any, any]
|
|
46
48
|
export function useValue$ (defaultValue?: any): any
|
|
47
49
|
export function useModel (path?: any): any
|
|
@@ -94,6 +96,8 @@ export function useDidUpdate (fn: () => EffectCleanup, deps?: any[]): void
|
|
|
94
96
|
export function useOnce (condition: any, fn: () => EffectCleanup): void
|
|
95
97
|
export function useSyncEffect (fn: () => EffectCleanup, deps?: any[]): void
|
|
96
98
|
export { connection, setConnection, getConnection, fetchOnly, setFetchOnly, publicOnly, setPublicOnly } from './orm/connection.js'
|
|
99
|
+
export function getSubscriptionGcDelay (): number
|
|
100
|
+
export function setSubscriptionGcDelay (ms?: number | null): number
|
|
97
101
|
export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.js'
|
|
98
102
|
export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema'
|
|
99
103
|
export { aggregation, aggregationHeader as __aggregationHeader } from '@teamplay/utils/aggregation'
|
package/index.js
CHANGED
|
@@ -23,6 +23,10 @@ export {
|
|
|
23
23
|
setUseDeferredValue as __setUseDeferredValue,
|
|
24
24
|
setDefaultDefer as __setDefaultDefer
|
|
25
25
|
} from './react/useSub.js'
|
|
26
|
+
export {
|
|
27
|
+
default as useSuspendMemo,
|
|
28
|
+
useSuspendMemoByKey
|
|
29
|
+
} from './react/useSuspendMemo.js'
|
|
26
30
|
export { default as observer } from './react/observer.js'
|
|
27
31
|
export {
|
|
28
32
|
useValue,
|
|
@@ -65,7 +69,16 @@ export {
|
|
|
65
69
|
useOnce,
|
|
66
70
|
useSyncEffect
|
|
67
71
|
} from './react/helpers.js'
|
|
68
|
-
export {
|
|
72
|
+
export {
|
|
73
|
+
connection,
|
|
74
|
+
setConnection,
|
|
75
|
+
getConnection,
|
|
76
|
+
getDefaultFetchOnly,
|
|
77
|
+
setDefaultFetchOnly,
|
|
78
|
+
publicOnly,
|
|
79
|
+
setPublicOnly
|
|
80
|
+
} from './orm/connection.js'
|
|
81
|
+
export { getSubscriptionGcDelay, setSubscriptionGcDelay } from './orm/subscriptionGcDelay.js'
|
|
69
82
|
export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.js'
|
|
70
83
|
export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema'
|
|
71
84
|
export { aggregation, aggregationHeader as __aggregationHeader } from '@teamplay/utils/aggregation'
|
package/orm/Aggregation.js
CHANGED
|
@@ -1,34 +1,54 @@
|
|
|
1
1
|
import { raw } from '@nx-js/observer-util'
|
|
2
|
-
import {
|
|
2
|
+
import { getRaw } from './dataTree.js'
|
|
3
3
|
import getSignal from './getSignal.js'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
QuerySubscriptions,
|
|
6
|
+
hashQuery,
|
|
7
|
+
Query,
|
|
8
|
+
HASH,
|
|
9
|
+
PARAMS,
|
|
10
|
+
COLLECTION_NAME,
|
|
11
|
+
parseQueryHash
|
|
12
|
+
} from './Query.js'
|
|
5
13
|
import Signal, { SEGMENTS } from './Signal.js'
|
|
6
14
|
import { getIdFieldsForSegments, isPlainObject } from './idFields.js'
|
|
15
|
+
import { delPrivateData, getPrivateData, setPrivateData } from './privateData.js'
|
|
7
16
|
|
|
8
17
|
export const IS_AGGREGATION = Symbol('is aggregation signal')
|
|
9
18
|
export const AGGREGATIONS = '$aggregations'
|
|
10
19
|
|
|
11
20
|
class Aggregation extends Query {
|
|
12
21
|
_initData () {
|
|
13
|
-
|
|
14
|
-
const extra = raw(this.shareQuery.extra)
|
|
15
|
-
injectAggregationIds(extra, this.collectionName)
|
|
16
|
-
_set([AGGREGATIONS, this.hash], extra)
|
|
17
|
-
}
|
|
22
|
+
this._syncAllRootsData()
|
|
18
23
|
|
|
19
24
|
this.shareQuery.on('extra', extra => {
|
|
20
25
|
extra = raw(extra)
|
|
21
26
|
injectAggregationIds(extra, this.collectionName)
|
|
22
|
-
|
|
27
|
+
this._forEachRoot(rootId => {
|
|
28
|
+
setPrivateData(rootId, [AGGREGATIONS, this.hash], extra)
|
|
29
|
+
})
|
|
23
30
|
})
|
|
24
31
|
}
|
|
25
32
|
|
|
33
|
+
_syncRootData (rootId) {
|
|
34
|
+
if (!this.shareQuery) return
|
|
35
|
+
const extra = raw(this.shareQuery.extra)
|
|
36
|
+
injectAggregationIds(extra, this.collectionName)
|
|
37
|
+
setPrivateData(rootId, [AGGREGATIONS, this.hash], extra)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_removeRootData (rootId) {
|
|
41
|
+
delPrivateData(rootId, [AGGREGATIONS, this.hash])
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
_removeData () {
|
|
27
|
-
|
|
45
|
+
this._forEachRoot(rootId => this._removeRootData(rootId))
|
|
46
|
+
this.rootIds.clear()
|
|
28
47
|
}
|
|
29
48
|
}
|
|
30
49
|
|
|
31
50
|
export const aggregationSubscriptions = new QuerySubscriptions(Aggregation)
|
|
51
|
+
aggregationSubscriptions.runtimeKind = 'aggregation'
|
|
32
52
|
|
|
33
53
|
function injectAggregationIds (extra, collectionName) {
|
|
34
54
|
if (!Array.isArray(extra)) return
|
|
@@ -44,13 +64,14 @@ function injectAggregationIds (extra, collectionName) {
|
|
|
44
64
|
|
|
45
65
|
export function getAggregationSignal (collectionName, params, options) {
|
|
46
66
|
params = JSON.parse(JSON.stringify(params))
|
|
47
|
-
const
|
|
67
|
+
const transportHash = hashQuery(collectionName, params)
|
|
68
|
+
const { root, signalOptions } = parseAggregationSignalOptions(options)
|
|
48
69
|
|
|
49
|
-
const $aggregation = getSignal(
|
|
70
|
+
const $aggregation = getSignal(root, [AGGREGATIONS, transportHash], signalOptions)
|
|
50
71
|
$aggregation[IS_AGGREGATION] ??= true
|
|
51
72
|
$aggregation[COLLECTION_NAME] ??= collectionName
|
|
52
73
|
$aggregation[PARAMS] ??= params
|
|
53
|
-
$aggregation[HASH] ??=
|
|
74
|
+
$aggregation[HASH] ??= transportHash
|
|
54
75
|
return $aggregation
|
|
55
76
|
}
|
|
56
77
|
|
|
@@ -65,10 +86,13 @@ export function isAggregationSignal ($signal) {
|
|
|
65
86
|
|
|
66
87
|
// example: ['$aggregations', '{"active":true}', 42]
|
|
67
88
|
// AND only if it also has either '_id' or 'id' field inside
|
|
68
|
-
export function getAggregationDocId (segments, method
|
|
89
|
+
export function getAggregationDocId (segments, rootId, method) {
|
|
69
90
|
if (!(segments.length >= 3)) return
|
|
70
91
|
if (!(segments[0] === AGGREGATIONS)) return
|
|
71
92
|
if (!(typeof segments[2] === 'number')) return
|
|
93
|
+
if (typeof method !== 'function') {
|
|
94
|
+
method = path => rootId == null ? getRaw(path) : getPrivateData(rootId, path)
|
|
95
|
+
}
|
|
72
96
|
const docId = method([...segments.slice(0, 3), '_id']) || method([...segments.slice(0, 3), 'id'])
|
|
73
97
|
return docId
|
|
74
98
|
}
|
|
@@ -80,3 +104,14 @@ export function getAggregationCollectionName (segments) {
|
|
|
80
104
|
const { collectionName } = parseQueryHash(hash)
|
|
81
105
|
return collectionName
|
|
82
106
|
}
|
|
107
|
+
|
|
108
|
+
function parseAggregationSignalOptions (options) {
|
|
109
|
+
if (!options || typeof options !== 'object') {
|
|
110
|
+
return {
|
|
111
|
+
root: undefined,
|
|
112
|
+
signalOptions: {}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const { root, ...signalOptions } = options
|
|
116
|
+
return { root, signalOptions }
|
|
117
|
+
}
|