teamplay 0.4.0-alpha.21 → 0.4.0-alpha.23
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/orm/Compat/README.md +40 -0
- package/orm/Compat/hooksCompat.js +12 -2
- package/orm/Compat/startStopCompat.js +85 -0
- package/orm/SignalBase.js +17 -0
- package/package.json +2 -2
package/orm/Compat/README.md
CHANGED
|
@@ -174,6 +174,46 @@ $alias.get() === $user.get() // false
|
|
|
174
174
|
- No event emissions specific to refs.
|
|
175
175
|
- No support for racer-style ref meta/options beyond the basic signature.
|
|
176
176
|
|
|
177
|
+
### start(targetPath, ...deps, getter)
|
|
178
|
+
|
|
179
|
+
Legacy computed binding API from Racer/StartupJS.
|
|
180
|
+
Creates a reactive computation and writes its result into `targetPath`.
|
|
181
|
+
Source of truth is root API (`$root.start(...)`), but non-root calls are supported as sugar:
|
|
182
|
+
- `$scope.start('a.b', ...deps, getter)` → `$root.start('<scopePath>.a.b', ...deps, getter)`
|
|
183
|
+
|
|
184
|
+
- `targetPath`: string path where computed value is written.
|
|
185
|
+
- `deps`: dependencies used by `getter`.
|
|
186
|
+
- `getter`: function called as `getter(...resolvedDeps)`.
|
|
187
|
+
|
|
188
|
+
Dependency resolution:
|
|
189
|
+
- Signal-like dep (`$doc`, `$session.user`) → `dep.get()`.
|
|
190
|
+
- String dep (`'settings.theme'`) → `$root.get(dep)`.
|
|
191
|
+
- Any other dep → passed as-is.
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
$root.start('_virtual.lesson', $.lessons[lessonId], '_session.userId', (lesson, userId) => {
|
|
195
|
+
if (!lesson) return undefined
|
|
196
|
+
return { stageIds: lesson.stageIds, userId }
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Behavior:
|
|
201
|
+
- Calling `start()` again for the same `targetPath` replaces previous reaction.
|
|
202
|
+
- `undefined` / `null` result clears target path via normal `set` semantics.
|
|
203
|
+
- Returns target signal.
|
|
204
|
+
|
|
205
|
+
### stop(targetPath)
|
|
206
|
+
|
|
207
|
+
Stops a computation created with `start(targetPath, ...)`.
|
|
208
|
+
No-op if there is no active computation for the path.
|
|
209
|
+
Source of truth is root API (`$root.stop(...)`), but non-root calls are supported as sugar:
|
|
210
|
+
- `$scope.stop('a.b')` → `$root.stop('<scopePath>.a.b')`
|
|
211
|
+
- `$scope.stop()` → `$root.stop('<scopePath>')`
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
$root.stop('_virtual.lesson')
|
|
215
|
+
```
|
|
216
|
+
|
|
177
217
|
### query(collection, query, options?)
|
|
178
218
|
|
|
179
219
|
Creates a query signal **without** subscribing. Supports shorthand params:
|
|
@@ -97,7 +97,8 @@ export function useBatchDoc (collection, id, options) {
|
|
|
97
97
|
|
|
98
98
|
export function useBatchDoc$ (collection, id, _options) {
|
|
99
99
|
const $doc = getDocSignal(collection, id, 'useBatchDoc')
|
|
100
|
-
|
|
100
|
+
const options = _options ? { ..._options, ...BATCH_SUB_OPTIONS } : BATCH_SUB_OPTIONS
|
|
101
|
+
return useSub($doc, undefined, options)
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
export function useAsyncDoc$ (collection, id, options) {
|
|
@@ -140,7 +141,8 @@ export function useAsyncQuery (collection, query, options) {
|
|
|
140
141
|
|
|
141
142
|
export function useBatchQuery$ (collection, query, _options) {
|
|
142
143
|
const $collection = getCollectionSignal(collection, query, 'useBatchQuery')
|
|
143
|
-
|
|
144
|
+
const options = _options ? { ..._options, ...BATCH_SUB_OPTIONS } : BATCH_SUB_OPTIONS
|
|
145
|
+
return useSub($collection, normalizeQuery(query, 'useBatchQuery'), options)
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
export function useBatchQuery (collection, query, options) {
|
|
@@ -306,3 +308,11 @@ function normalizeQuery (query, hookName) {
|
|
|
306
308
|
}
|
|
307
309
|
return query
|
|
308
310
|
}
|
|
311
|
+
|
|
312
|
+
const BATCH_SUB_OPTIONS = Object.freeze({
|
|
313
|
+
async: false,
|
|
314
|
+
batch: true,
|
|
315
|
+
// Batch hooks are a hard suspense barrier. Deferred params can skip the barrier
|
|
316
|
+
// on route transitions and cause immediate reads from stale/empty local nodes.
|
|
317
|
+
defer: false
|
|
318
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { observe, unobserve } from '@nx-js/observer-util'
|
|
2
|
+
import { getRoot } from '../Root.js'
|
|
3
|
+
|
|
4
|
+
const START_REACTIONS = Symbol('compat start reactions')
|
|
5
|
+
|
|
6
|
+
export function compatStartOnRoot ($root, targetPath, ...depsAndGetter) {
|
|
7
|
+
if (!isRootSignal($root)) throw Error('Signal.start() is only available on root signal')
|
|
8
|
+
if (typeof targetPath !== 'string') throw Error('Signal.start() expects targetPath to be a string')
|
|
9
|
+
if (depsAndGetter.length < 1) {
|
|
10
|
+
throw Error('Signal.start() expects targetPath, dependencies, and a getter function')
|
|
11
|
+
}
|
|
12
|
+
const getter = depsAndGetter[depsAndGetter.length - 1]
|
|
13
|
+
if (typeof getter !== 'function') {
|
|
14
|
+
throw Error('Signal.start() expects the last argument to be a getter function')
|
|
15
|
+
}
|
|
16
|
+
const deps = depsAndGetter.slice(0, -1)
|
|
17
|
+
const targetSegments = parsePathSegments(targetPath)
|
|
18
|
+
const $target = resolveSignal($root, targetSegments)
|
|
19
|
+
const targetKey = $target.path()
|
|
20
|
+
|
|
21
|
+
const store = getStartStore($root)
|
|
22
|
+
const existing = store.get(targetKey)
|
|
23
|
+
if (existing) existing.stop()
|
|
24
|
+
|
|
25
|
+
const reaction = observe(() => {
|
|
26
|
+
const resolvedDeps = deps.map(dep => resolveStartDep(dep, $root))
|
|
27
|
+
const nextValue = getter(...resolvedDeps)
|
|
28
|
+
const maybePromise = $target.set(nextValue)
|
|
29
|
+
if (maybePromise?.catch) maybePromise.catch(ignorePromiseRejection)
|
|
30
|
+
})
|
|
31
|
+
store.set(targetKey, { stop: () => unobserve(reaction) })
|
|
32
|
+
return $target
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function compatStopOnRoot ($root, targetPath) {
|
|
36
|
+
if (!isRootSignal($root)) throw Error('Signal.stop() is only available on root signal')
|
|
37
|
+
if (typeof targetPath !== 'string') throw Error('Signal.stop() expects targetPath to be a string')
|
|
38
|
+
const targetSegments = parsePathSegments(targetPath)
|
|
39
|
+
const $target = resolveSignal($root, targetSegments)
|
|
40
|
+
const targetKey = $target.path()
|
|
41
|
+
const store = getStartStore($root)
|
|
42
|
+
const existing = store.get(targetKey)
|
|
43
|
+
if (!existing) return
|
|
44
|
+
existing.stop()
|
|
45
|
+
store.delete(targetKey)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function joinScopePath (scopePath, relativePath) {
|
|
49
|
+
if (typeof scopePath !== 'string') scopePath = ''
|
|
50
|
+
const segments = []
|
|
51
|
+
if (scopePath) segments.push(...parsePathSegments(scopePath))
|
|
52
|
+
if (relativePath) segments.push(...parsePathSegments(relativePath))
|
|
53
|
+
return segments.join('.')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getStartStore ($root) {
|
|
57
|
+
$root[START_REACTIONS] ??= new Map()
|
|
58
|
+
return $root[START_REACTIONS]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveStartDep (dep, $root) {
|
|
62
|
+
if (isSignalLike(dep)) return dep.get()
|
|
63
|
+
if (typeof dep === 'string') return resolveSignal($root, parsePathSegments(dep)).get()
|
|
64
|
+
return dep
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isSignalLike (value) {
|
|
68
|
+
return value && typeof value.path === 'function' && typeof value.get === 'function'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parsePathSegments (path) {
|
|
72
|
+
return path.split('.').filter(Boolean)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveSignal ($base, segments) {
|
|
76
|
+
let $cursor = $base
|
|
77
|
+
for (const segment of segments) $cursor = $cursor[segment]
|
|
78
|
+
return $cursor
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isRootSignal ($signal) {
|
|
82
|
+
return getRoot($signal) === $signal
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ignorePromiseRejection () {}
|
package/orm/SignalBase.js
CHANGED
|
@@ -48,6 +48,7 @@ import { publicOnly } from './connection.js'
|
|
|
48
48
|
import { DEFAULT_ID_FIELDS, getIdFieldsForSegments, isIdFieldPath, normalizeIdFields } from './idFields.js'
|
|
49
49
|
import { isCompatEnv } from './compatEnv.js'
|
|
50
50
|
import { resolveRefSegmentsSafe, resolveRefSignalSafe } from './Compat/refFallback.js'
|
|
51
|
+
import { compatStartOnRoot, compatStopOnRoot, joinScopePath } from './Compat/startStopCompat.js'
|
|
51
52
|
|
|
52
53
|
export const SEGMENTS = Symbol('path segments targeting the particular node in the data tree')
|
|
53
54
|
export const ARRAY_METHOD = Symbol('run array method on the signal')
|
|
@@ -522,6 +523,22 @@ export const extremelyLateBindings = {
|
|
|
522
523
|
}
|
|
523
524
|
}
|
|
524
525
|
}
|
|
526
|
+
|
|
527
|
+
if (key === 'start') {
|
|
528
|
+
const [relativePath, ...depsAndGetter] = argumentsList
|
|
529
|
+
if (typeof relativePath !== 'string') throw Error('Signal.start() expects targetPath to be a string')
|
|
530
|
+
const absolutePath = joinScopePath($parent.path(), relativePath)
|
|
531
|
+
return compatStartOnRoot(getRoot($parent) || $parent, absolutePath, ...depsAndGetter)
|
|
532
|
+
}
|
|
533
|
+
if (key === 'stop') {
|
|
534
|
+
if (argumentsList.length > 1) throw Error('Signal.stop() expects zero or one argument')
|
|
535
|
+
const relativePath = argumentsList.length === 0 ? '' : argumentsList[0]
|
|
536
|
+
if (relativePath != null && typeof relativePath !== 'string') {
|
|
537
|
+
throw Error('Signal.stop() expects targetPath to be a string')
|
|
538
|
+
}
|
|
539
|
+
const absolutePath = joinScopePath($parent.path(), relativePath || '')
|
|
540
|
+
return compatStopOnRoot(getRoot($parent) || $parent, absolutePath)
|
|
541
|
+
}
|
|
525
542
|
}
|
|
526
543
|
|
|
527
544
|
throw Error(ERRORS.noSignalKey($parent, key))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamplay",
|
|
3
|
-
"version": "0.4.0-alpha.
|
|
3
|
+
"version": "0.4.0-alpha.23",
|
|
4
4
|
"description": "Full-stack signals ORM with multiplayer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
]
|
|
82
82
|
},
|
|
83
83
|
"license": "MIT",
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "bcf9b46727a5fe9ee3836e582bf25b933fc7d479"
|
|
85
85
|
}
|