teamplay 0.5.0-alpha.13 → 0.5.0-alpha.14
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.
|
@@ -247,7 +247,7 @@ function isExtraQuery(query) {
|
|
|
247
247
|
const BATCH_SUB_OPTIONS = Object.freeze({
|
|
248
248
|
async: false,
|
|
249
249
|
batch: true,
|
|
250
|
-
|
|
250
|
+
renderAttemptCleanup: true,
|
|
251
251
|
// Batch hooks are a hard suspense barrier. Deferred params can skip the barrier
|
|
252
252
|
// on route transitions and cause immediate reads from stale/empty local nodes.
|
|
253
253
|
defer: false
|
|
@@ -259,7 +259,7 @@ function normalizeSyncSubOptions(options) {
|
|
|
259
259
|
return {
|
|
260
260
|
...(options || {}),
|
|
261
261
|
async: false,
|
|
262
|
-
|
|
262
|
+
renderAttemptCleanup: true,
|
|
263
263
|
// Compat sync hooks are strict by design: no deferred snapshots between route/tab switches.
|
|
264
264
|
defer: false
|
|
265
265
|
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
type DestroyAttempt = () => unknown | Promise<unknown>;
|
|
2
2
|
declare class RenderAttemptDestroyer {
|
|
3
3
|
fns: DestroyAttempt[];
|
|
4
|
-
|
|
4
|
+
renderAttemptCleanupArmed: boolean;
|
|
5
5
|
suspenseGateArmed: boolean;
|
|
6
6
|
constructor();
|
|
7
|
-
add(fn: DestroyAttempt | undefined, {
|
|
8
|
-
|
|
7
|
+
add(fn: DestroyAttempt | undefined, { renderAttemptCleanup }?: {
|
|
8
|
+
renderAttemptCleanup?: boolean;
|
|
9
9
|
}): void;
|
|
10
|
-
|
|
10
|
+
armRenderAttemptCleanup(): void;
|
|
11
11
|
armSuspenseGate(): void;
|
|
12
12
|
consumeThenableHandling(): {
|
|
13
13
|
shouldKeepShellAlive: boolean;
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
class RenderAttemptDestroyer {
|
|
2
2
|
fns;
|
|
3
|
-
|
|
3
|
+
renderAttemptCleanupArmed;
|
|
4
4
|
suspenseGateArmed;
|
|
5
5
|
constructor() {
|
|
6
6
|
this.fns = [];
|
|
7
|
-
this.
|
|
7
|
+
this.renderAttemptCleanupArmed = false;
|
|
8
8
|
this.suspenseGateArmed = false;
|
|
9
9
|
}
|
|
10
|
-
add(fn, {
|
|
10
|
+
add(fn, { renderAttemptCleanup = false } = {}) {
|
|
11
11
|
if (typeof fn !== 'function')
|
|
12
12
|
return;
|
|
13
13
|
this.fns.push(fn);
|
|
14
|
-
if (
|
|
15
|
-
this.
|
|
14
|
+
if (renderAttemptCleanup)
|
|
15
|
+
this.renderAttemptCleanupArmed = true;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
this.
|
|
17
|
+
armRenderAttemptCleanup() {
|
|
18
|
+
this.renderAttemptCleanupArmed = true;
|
|
19
19
|
}
|
|
20
20
|
armSuspenseGate() {
|
|
21
21
|
this.suspenseGateArmed = true;
|
|
22
22
|
}
|
|
23
23
|
consumeThenableHandling() {
|
|
24
|
-
const shouldRunAttemptCleanup = this.
|
|
24
|
+
const shouldRunAttemptCleanup = this.renderAttemptCleanupArmed && this.fns.length > 0;
|
|
25
25
|
const shouldKeepShellAlive = this.suspenseGateArmed || shouldRunAttemptCleanup;
|
|
26
26
|
let destroyAttempt;
|
|
27
27
|
if (shouldRunAttemptCleanup) {
|
|
@@ -39,7 +39,7 @@ class RenderAttemptDestroyer {
|
|
|
39
39
|
}
|
|
40
40
|
reset() {
|
|
41
41
|
this.fns.length = 0;
|
|
42
|
-
this.
|
|
42
|
+
this.renderAttemptCleanupArmed = false;
|
|
43
43
|
this.suspenseGateArmed = false;
|
|
44
44
|
}
|
|
45
45
|
}
|
package/dist/react/useSub.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export interface UseSubOptions {
|
|
|
7
7
|
defer?: boolean | number;
|
|
8
8
|
/** Batch Suspense promises across multiple subscriptions in one render attempt. */
|
|
9
9
|
batch?: boolean;
|
|
10
|
-
/** Enable
|
|
11
|
-
|
|
10
|
+
/** Enable cleanup for observer render attempts when a subscription suspends. */
|
|
11
|
+
renderAttemptCleanup?: boolean;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Subscribe to a document signal in React async mode.
|
|
@@ -112,8 +112,8 @@ export default function useSub<TDocument, TDocumentModel extends SignalModelCons
|
|
|
112
112
|
* @param options Subscription behavior options.
|
|
113
113
|
*/
|
|
114
114
|
export default function useSub<TOutput = unknown, TCollection extends string = string>(signal: AggregationFunction<TOutput, TCollection>, params?: AggregationParams, options?: UseSubOptions): SubResult<AggregationFunction<TOutput, TCollection>, AggregationParams | undefined>;
|
|
115
|
-
export declare function useSubDeferred(signal: unknown, params?: unknown, { async, defer, batch,
|
|
116
|
-
export declare function useSubClassic(signal: unknown, params?: unknown, { async, batch,
|
|
115
|
+
export declare function useSubDeferred(signal: unknown, params?: unknown, { async, defer, batch, renderAttemptCleanup }?: UseSubOptions): unknown;
|
|
116
|
+
export declare function useSubClassic(signal: unknown, params?: unknown, { async, batch, renderAttemptCleanup }?: UseSubOptions): unknown;
|
|
117
117
|
export declare function setTestThrottling(ms: number): void;
|
|
118
118
|
export declare function resetTestThrottling(): void;
|
|
119
119
|
export declare function setUseDeferredValue(value: boolean): void;
|
package/dist/react/useSub.js
CHANGED
|
@@ -27,12 +27,12 @@ function runUseSub(signal, params, options) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
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,
|
|
30
|
+
export function useSubDeferred(signal, params, { async = false, defer, batch = false, renderAttemptCleanup = false } = {}) {
|
|
31
31
|
const $signalRef = useRef();
|
|
32
32
|
const componentId = useId();
|
|
33
33
|
const scheduleUpdate = useScheduleUpdate();
|
|
34
34
|
const observerDefer = useDefer();
|
|
35
|
-
if (
|
|
35
|
+
if (renderAttemptCleanup)
|
|
36
36
|
markCompatComponent(componentId);
|
|
37
37
|
if (batch)
|
|
38
38
|
promiseBatcher.activate();
|
|
@@ -52,8 +52,8 @@ export function useSubDeferred(signal, params, { async = false, defer, batch = f
|
|
|
52
52
|
// On resubscribe we keep rendering previous signal and refresh in background.
|
|
53
53
|
if (!hasPreviousSignal) {
|
|
54
54
|
promiseBatcher.add(promise);
|
|
55
|
-
if (
|
|
56
|
-
|
|
55
|
+
if (renderAttemptCleanup)
|
|
56
|
+
registerRenderAttemptCleanup(signal, params);
|
|
57
57
|
}
|
|
58
58
|
else {
|
|
59
59
|
scheduleUpdate(promise);
|
|
@@ -71,8 +71,8 @@ export function useSubDeferred(signal, params, { async = false, defer, batch = f
|
|
|
71
71
|
scheduleUpdate(promise);
|
|
72
72
|
return $signalRef.current;
|
|
73
73
|
}
|
|
74
|
-
if (
|
|
75
|
-
|
|
74
|
+
if (renderAttemptCleanup)
|
|
75
|
+
registerRenderAttemptCleanup(signal, params);
|
|
76
76
|
throw promise;
|
|
77
77
|
// 2. if it's a signal, we save it into ref to make sure it's not garbage collected while component exists
|
|
78
78
|
}
|
|
@@ -85,12 +85,12 @@ export function useSubDeferred(signal, params, { async = false, defer, batch = f
|
|
|
85
85
|
}
|
|
86
86
|
// classic version which initially throws promise for Suspense
|
|
87
87
|
// but if we get a promise second time, we return the last signal and wait for promise to resolve
|
|
88
|
-
export function useSubClassic(signal, params, { async = false, batch = false,
|
|
88
|
+
export function useSubClassic(signal, params, { async = false, batch = false, renderAttemptCleanup = false } = {}) {
|
|
89
89
|
const id = executionContextTracker.newHookId();
|
|
90
90
|
const componentId = useId();
|
|
91
91
|
const cache = useCache(undefined);
|
|
92
92
|
const scheduleUpdate = useScheduleUpdate();
|
|
93
|
-
if (
|
|
93
|
+
if (renderAttemptCleanup)
|
|
94
94
|
markCompatComponent(componentId);
|
|
95
95
|
if (batch)
|
|
96
96
|
promiseBatcher.activate();
|
|
@@ -104,8 +104,8 @@ export function useSubClassic(signal, params, { async = false, batch = false, co
|
|
|
104
104
|
// On resubscribe we keep rendering previous signal and refresh in background.
|
|
105
105
|
if (!hasPreviousSignal) {
|
|
106
106
|
promiseBatcher.add(promise);
|
|
107
|
-
if (
|
|
108
|
-
|
|
107
|
+
if (renderAttemptCleanup)
|
|
108
|
+
registerRenderAttemptCleanup(signal, params);
|
|
109
109
|
}
|
|
110
110
|
else {
|
|
111
111
|
scheduleUpdate(promise);
|
|
@@ -126,8 +126,8 @@ export function useSubClassic(signal, params, { async = false, batch = false, co
|
|
|
126
126
|
scheduleUpdate(promise);
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
|
-
if (
|
|
130
|
-
|
|
129
|
+
if (renderAttemptCleanup)
|
|
130
|
+
registerRenderAttemptCleanup(signal, params);
|
|
131
131
|
// in regular mode we throw the promise to be caught by Suspense
|
|
132
132
|
// this way we guarantee that the signal with all the data
|
|
133
133
|
// will always be there when component is rendered
|
|
@@ -180,10 +180,10 @@ function isThenable(value) {
|
|
|
180
180
|
(typeof value === 'object' || typeof value === 'function') &&
|
|
181
181
|
typeof value.then === 'function';
|
|
182
182
|
}
|
|
183
|
-
function
|
|
184
|
-
//
|
|
183
|
+
function registerRenderAttemptCleanup(_signal, _params) {
|
|
184
|
+
// Legacy hooks don't build per-hook init objects like Racer.
|
|
185
185
|
// We still need a marker so trapRender can defer observer-shell cleanup
|
|
186
186
|
// only when a real attempt cleanup exists.
|
|
187
187
|
// This path must not arm suspense-gate keep-alive by itself.
|
|
188
|
-
renderAttemptDestroyer.
|
|
188
|
+
renderAttemptDestroyer.armRenderAttemptCleanup();
|
|
189
189
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamplay",
|
|
3
|
-
"version": "0.5.0-alpha.
|
|
3
|
+
"version": "0.5.0-alpha.14",
|
|
4
4
|
"description": "Full-stack signals ORM with multiplayer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -134,5 +134,5 @@
|
|
|
134
134
|
]
|
|
135
135
|
},
|
|
136
136
|
"license": "MIT",
|
|
137
|
-
"gitHead": "
|
|
137
|
+
"gitHead": "84185ddad8cdbac3d2dfcc48cff3588e5ab99630"
|
|
138
138
|
}
|