teamplay 0.5.0-alpha.17 → 0.5.0-alpha.18
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/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/orm/Compat/queryReadiness.d.ts +13 -5
- package/dist/orm/Query.d.ts +1 -0
- package/dist/orm/Query.js +18 -0
- package/dist/orm/privateData.d.ts +7 -22
- package/dist/react/promiseBatcher.js +1 -1
- package/dist/react/trapRender.js +1 -1
- package/dist/react/useSub.d.ts +69 -0
- package/dist/react/useSub.js +167 -4
- package/package.json +2 -2
- package/dist/orm/Compat/hooksCompat.d.ts +0 -8
- package/dist/orm/Compat/hooksCompat.js +0 -151
package/dist/index.d.ts
CHANGED
|
@@ -54,10 +54,9 @@ export { GLOBAL_ROOT_ID } from './orm/Root.js';
|
|
|
54
54
|
export declare const $: RootSignal;
|
|
55
55
|
export default $;
|
|
56
56
|
export { default as sub } from './orm/sub.js';
|
|
57
|
-
export { default as useSub, useAsyncSub, setUseDeferredValue as __setUseDeferredValue, setDefaultDefer as __setDefaultDefer } from './react/useSub.js';
|
|
57
|
+
export { default as useSub, useAsyncSub, useBatchSub, setUseDeferredValue as __setUseDeferredValue, setDefaultDefer as __setDefaultDefer } from './react/useSub.js';
|
|
58
58
|
export { default as useSuspendMemo, useSuspendMemoByKey } from './react/useSuspendMemo.js';
|
|
59
59
|
export declare const observer: ObserverFunction;
|
|
60
|
-
export { useBatch, useBatchDoc, useBatchDoc$, useBatchQuery, useBatchQuery$, useBatchQueryIds, useBatchQueryDoc, useBatchQueryDoc$ } from './orm/Compat/hooksCompat.js';
|
|
61
60
|
export { emit, useOn, useEmit } from './orm/Compat/eventsCompat.js';
|
|
62
61
|
export { useDidUpdate, useOnce, useSyncEffect } from './react/helpers.js';
|
|
63
62
|
export { connection, setConnection, getConnection, getDefaultFetchOnly, setDefaultFetchOnly, publicOnly, setPublicOnly } from './orm/connection.js';
|
package/dist/index.js
CHANGED
|
@@ -15,10 +15,9 @@ const getRuntimeRootSignal = _getRootSignal;
|
|
|
15
15
|
export const $ = getRuntimeRootSignal({ rootId: GLOBAL_ROOT_ID, rootFunction: universal$ });
|
|
16
16
|
export default $;
|
|
17
17
|
export { default as sub } from "./orm/sub.js";
|
|
18
|
-
export { default as useSub, useAsyncSub, setUseDeferredValue as __setUseDeferredValue, setDefaultDefer as __setDefaultDefer } from "./react/useSub.js";
|
|
18
|
+
export { default as useSub, useAsyncSub, useBatchSub, setUseDeferredValue as __setUseDeferredValue, setDefaultDefer as __setDefaultDefer } from "./react/useSub.js";
|
|
19
19
|
export { default as useSuspendMemo, useSuspendMemoByKey } from "./react/useSuspendMemo.js";
|
|
20
20
|
export const observer = runtimeObserver;
|
|
21
|
-
export { useBatch, useBatchDoc, useBatchDoc$, useBatchQuery, useBatchQuery$, useBatchQueryIds, useBatchQueryDoc, useBatchQueryDoc$ } from './orm/Compat/hooksCompat.js';
|
|
22
21
|
export { emit, useOn, useEmit } from './orm/Compat/eventsCompat.js';
|
|
23
22
|
export { useDidUpdate, useOnce, useSyncEffect } from "./react/helpers.js";
|
|
24
23
|
export { connection, setConnection, getConnection, getDefaultFetchOnly, setDefaultFetchOnly, publicOnly, setPublicOnly } from "./orm/connection.js";
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export function
|
|
4
|
-
|
|
5
|
-
export function
|
|
1
|
+
import type { PathSegment } from '../types/path.js'
|
|
2
|
+
|
|
3
|
+
export function isDocReady (segments: readonly PathSegment[]): boolean
|
|
4
|
+
|
|
5
|
+
export function isQueryReady (
|
|
6
|
+
collection: string,
|
|
7
|
+
idsSegments: readonly PathSegment[],
|
|
8
|
+
docsSegments: readonly PathSegment[],
|
|
9
|
+
extraSegments: readonly PathSegment[],
|
|
10
|
+
aggregationSegments: readonly PathSegment[],
|
|
11
|
+
isAggregate: boolean,
|
|
12
|
+
hasExtraResult: boolean
|
|
13
|
+
): boolean
|
package/dist/orm/Query.d.ts
CHANGED
|
@@ -113,5 +113,6 @@ export class QuerySubscriptions {
|
|
|
113
113
|
|
|
114
114
|
export const querySubscriptions: QuerySubscriptions
|
|
115
115
|
export function getQuerySignal (collectionName: string, params: unknown, options?: QuerySignalOptions): Signal
|
|
116
|
+
export function materializeQueryDataDocsToCollection (collectionName: string, docs: unknown): void
|
|
116
117
|
export function hashQuery (collectionName: string, params: unknown): string
|
|
117
118
|
export function parseQueryHash (hash: string): QueryHashParts
|
package/dist/orm/Query.js
CHANGED
|
@@ -901,6 +901,24 @@ function maybeMaterializeQueryDocsToCollection(collectionName, shareDocs) {
|
|
|
901
901
|
_set([collectionName, doc.id], raw(doc.data));
|
|
902
902
|
}
|
|
903
903
|
}
|
|
904
|
+
export function materializeQueryDataDocsToCollection(collectionName, docs) {
|
|
905
|
+
if (!Array.isArray(docs))
|
|
906
|
+
return;
|
|
907
|
+
for (const doc of docs) {
|
|
908
|
+
const rawDoc = raw(doc);
|
|
909
|
+
if (!isPlainObject(rawDoc))
|
|
910
|
+
continue;
|
|
911
|
+
const docId = rawDoc._id ?? rawDoc.id;
|
|
912
|
+
if (docId == null)
|
|
913
|
+
continue;
|
|
914
|
+
const existing = getRaw([collectionName, docId]);
|
|
915
|
+
if (existing != null)
|
|
916
|
+
continue;
|
|
917
|
+
const idFields = getIdFieldsForSegments([collectionName, docId]);
|
|
918
|
+
injectIdFields(rawDoc, idFields, docId);
|
|
919
|
+
_set([collectionName, docId], rawDoc);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
904
922
|
export function hashQuery(collectionName, params) {
|
|
905
923
|
params = normalizeQueryParamsForHash(params);
|
|
906
924
|
// TODO: probably makes sense to use fast-stable-json-stringify for this because of the params
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
};
|
|
9
|
-
export function getPrivateData(rootId: any, logicalSegments: any, raw?: boolean): unknown;
|
|
10
|
-
export function setPrivateData(rootId: any, logicalSegments: any, value: any): void;
|
|
11
|
-
export function setReplacePrivateData(rootId: any, logicalSegments: any, value: any): void;
|
|
12
|
-
export function delPrivateData(rootId: any, logicalSegments: any, options?: {}): void;
|
|
13
|
-
export function arrayPushPrivateData(rootId: any, logicalSegments: any, value: any): number | undefined;
|
|
14
|
-
export function arrayUnshiftPrivateData(rootId: any, logicalSegments: any, value: any): number | undefined;
|
|
15
|
-
export function arrayInsertPrivateData(rootId: any, logicalSegments: any, index: any, values: any): number | undefined;
|
|
16
|
-
export function arrayPopPrivateData(rootId: any, logicalSegments: any): any;
|
|
17
|
-
export function arrayShiftPrivateData(rootId: any, logicalSegments: any): any;
|
|
18
|
-
export function arrayRemovePrivateData(rootId: any, logicalSegments: any, index: any, howMany?: number): any[] | undefined;
|
|
19
|
-
export function arrayMovePrivateData(rootId: any, logicalSegments: any, from: any, to: any, howMany?: number): any[] | undefined;
|
|
20
|
-
export function stringInsertPrivateData(rootId: any, logicalSegments: any, index: any, text: any): any;
|
|
21
|
-
export function stringRemovePrivateData(rootId: any, logicalSegments: any, index: any, howMany: any): any;
|
|
22
|
-
export function getPrivateDataSnapshot(rootId: any): any;
|
|
1
|
+
import type { PathSegment } from './types/path.js'
|
|
2
|
+
|
|
3
|
+
export function getPrivateData (
|
|
4
|
+
rootId: string | undefined,
|
|
5
|
+
logicalSegments: readonly PathSegment[],
|
|
6
|
+
raw?: boolean
|
|
7
|
+
): unknown
|
|
@@ -95,7 +95,7 @@ function warnAboutChecksDelay(checks) {
|
|
|
95
95
|
state
|
|
96
96
|
};
|
|
97
97
|
});
|
|
98
|
-
console.warn('[teamplay]
|
|
98
|
+
console.warn('[teamplay] useBatchSub() is waiting for data materialization checks.', details);
|
|
99
99
|
}
|
|
100
100
|
function isDevMode() {
|
|
101
101
|
const processLike = globalThis.process;
|
package/dist/react/trapRender.js
CHANGED
|
@@ -13,7 +13,7 @@ export default function trapRender({ render, cache, destroy, componentId }) {
|
|
|
13
13
|
promiseBatcher.reset();
|
|
14
14
|
const res = render(...args);
|
|
15
15
|
if (isDevMode() && promiseBatcher.isActive()) {
|
|
16
|
-
throw Error('[teamplay]
|
|
16
|
+
throw Error('[teamplay] batch subscriptions were used without a closing useBatchSub() call or useSub(undefined, undefined, { batch: true }) call.');
|
|
17
17
|
}
|
|
18
18
|
return res;
|
|
19
19
|
}
|
package/dist/react/useSub.d.ts
CHANGED
|
@@ -66,6 +66,75 @@ export declare function useAsyncSub<TDocument, TDocumentModel extends SignalMode
|
|
|
66
66
|
* @param options Subscription behavior options.
|
|
67
67
|
*/
|
|
68
68
|
export declare function useAsyncSub<TOutput = unknown, TCollection extends string = string>(signal: AggregationFunction<TOutput, TCollection>, params?: AggregationParams, options?: UseSubOptions): SubResult<AggregationFunction<TOutput, TCollection>, AggregationParams | undefined>;
|
|
69
|
+
/**
|
|
70
|
+
* Close a batch subscription barrier opened by previous `useBatchSub()` calls in this render.
|
|
71
|
+
*/
|
|
72
|
+
export declare function useBatchSub(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to a document signal in React batch mode.
|
|
75
|
+
* @param signal Document signal to subscribe to.
|
|
76
|
+
* @param params Must be omitted for document subscriptions.
|
|
77
|
+
* @param options Subscription behavior options.
|
|
78
|
+
*/
|
|
79
|
+
export declare function useBatchSub<TSignal extends DocumentSignal<any, any, any>>(signal: TSignal, options?: UseSubOptions): SubResult<TSignal>;
|
|
80
|
+
/**
|
|
81
|
+
* Subscribe to a document signal in React batch mode.
|
|
82
|
+
* @param signal Document signal to subscribe to.
|
|
83
|
+
* @param params Must be omitted for document subscriptions.
|
|
84
|
+
* @param options Subscription behavior options.
|
|
85
|
+
*/
|
|
86
|
+
export declare function useBatchSub<TSignal extends DocumentSignal<any, any, any>>(signal: TSignal, params?: undefined, options?: UseSubOptions): SubResult<TSignal>;
|
|
87
|
+
/**
|
|
88
|
+
* Subscribe to a collection query in React batch mode.
|
|
89
|
+
* @param signal Collection signal to query.
|
|
90
|
+
* @param params Mongo-style query params, including filters and `$sort`.
|
|
91
|
+
* @param options Subscription behavior options.
|
|
92
|
+
*/
|
|
93
|
+
export declare function useBatchSub<TDocument, TCollectionModel extends SignalModelConstructor<TDocument[]>, TDocumentModel extends SignalModelConstructor<TDocument>, TCollectionPath extends WildcardSignalPath>(signal: CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, params: QueryParams<TDocument>, options?: UseSubOptions): SubResult<CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, QueryParams<TDocument>>;
|
|
94
|
+
/**
|
|
95
|
+
* Subscribe to a collection query with computed string keys in React batch mode.
|
|
96
|
+
* This fallback preserves Mongo-style computed paths such as `{ [`likes.${id}`]: true }`.
|
|
97
|
+
* Literal query objects should use the stricter overload above.
|
|
98
|
+
* @param signal Collection signal to query.
|
|
99
|
+
* @param params Mongo-style query params with a widened computed key.
|
|
100
|
+
* @param options Subscription behavior options.
|
|
101
|
+
*/
|
|
102
|
+
export declare function useBatchSub<TDocument, TCollectionModel extends SignalModelConstructor<TDocument[]>, TDocumentModel extends SignalModelConstructor<TDocument>, TCollectionPath extends WildcardSignalPath, TParams extends object>(signal: CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, params: TParams & ComputedQueryParamsInput<TParams>, options?: UseSubOptions): SubResult<CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, TParams>;
|
|
103
|
+
/**
|
|
104
|
+
* Subscribe to a registered collection aggregation in React batch mode.
|
|
105
|
+
* @param signal Aggregation header generated by StartupJS model loading.
|
|
106
|
+
* @param params Parameters passed to the aggregation.
|
|
107
|
+
* @param options Subscription behavior options.
|
|
108
|
+
*/
|
|
109
|
+
export declare function useBatchSub<TCollection extends string, TOutput = unknown>(signal: RegisteredAggregationInput<TCollection, TOutput>, params?: AggregationParams, options?: UseSubOptions): SubResult<RegisteredAggregationInput<TCollection, TOutput>>;
|
|
110
|
+
/**
|
|
111
|
+
* Subscribe to a client aggregation in React batch mode.
|
|
112
|
+
* @param signal Aggregation function created with `aggregation(collection, fn)`.
|
|
113
|
+
* @param params Parameters passed to the aggregation.
|
|
114
|
+
* @param options Subscription behavior options.
|
|
115
|
+
*/
|
|
116
|
+
export declare function useBatchSub<TOutput, TCollection extends string>(signal: ClientAggregationFunction<TOutput, TCollection>, params?: AggregationParams, options?: UseSubOptions): SubResult<ClientAggregationFunction<TOutput, TCollection>>;
|
|
117
|
+
/**
|
|
118
|
+
* Subscribe to an aggregation with explicit output typing in React batch mode.
|
|
119
|
+
* @param signal Typed aggregation input.
|
|
120
|
+
* @param params Parameters passed to the aggregation.
|
|
121
|
+
* @param options Subscription behavior options.
|
|
122
|
+
*/
|
|
123
|
+
export declare function useBatchSub<TDocument, TDocumentModel extends SignalModelConstructor<TDocument>>(signal: TypedAggregationInput<TDocument, TDocumentModel>, params?: AggregationParams, options?: UseSubOptions): TypedAggregationSignal<TDocument, TDocumentModel>;
|
|
124
|
+
/**
|
|
125
|
+
* Subscribe to an unregistered aggregation in React batch mode.
|
|
126
|
+
* @param signal Aggregation function.
|
|
127
|
+
* @param params Parameters passed to the aggregation.
|
|
128
|
+
* @param options Subscription behavior options.
|
|
129
|
+
*/
|
|
130
|
+
export declare function useBatchSub<TOutput = unknown, TCollection extends string = string>(signal: AggregationFunction<TOutput, TCollection>, params?: AggregationParams, options?: UseSubOptions): SubResult<AggregationFunction<TOutput, TCollection>, AggregationParams | undefined>;
|
|
131
|
+
/**
|
|
132
|
+
* Close a batch subscription barrier opened by previous `useSub(..., { batch: true })`
|
|
133
|
+
* calls in this render.
|
|
134
|
+
*/
|
|
135
|
+
export default function useSub(signal: undefined, params: undefined, options: UseSubOptions & {
|
|
136
|
+
batch: true;
|
|
137
|
+
}): void;
|
|
69
138
|
/**
|
|
70
139
|
* Subscribe to a document signal in React.
|
|
71
140
|
* @param signal Document signal to subscribe to.
|
package/dist/react/useSub.js
CHANGED
|
@@ -3,6 +3,12 @@ import sub from "../orm/sub.js";
|
|
|
3
3
|
import { useScheduleUpdate, useCache, useDefer } from "./helpers.js";
|
|
4
4
|
import executionContextTracker from "./executionContextTracker.js";
|
|
5
5
|
import * as promiseBatcher from "./promiseBatcher.js";
|
|
6
|
+
import { getPrivateData } from '../orm/privateData.js';
|
|
7
|
+
import { isDocReady } from '../orm/Compat/queryReadiness.js';
|
|
8
|
+
import { getRoot, ROOT_ID } from "../orm/Root.js";
|
|
9
|
+
import { COLLECTION_NAME, HASH, IS_QUERY, PARAMS, QUERIES, querySubscriptions, materializeQueryDataDocsToCollection } from '../orm/Query.js';
|
|
10
|
+
import { AGGREGATIONS, IS_AGGREGATION, aggregationSubscriptions } from '../orm/Aggregation.js';
|
|
11
|
+
import { SEGMENTS } from "../orm/signalSymbols.js";
|
|
6
12
|
import { isPublicDocumentSignal } from "../orm/Signal.js";
|
|
7
13
|
const runtimeSub = sub;
|
|
8
14
|
const USE_SUB_OPTION_KEYS = new Set(['async', 'defer', 'batch']);
|
|
@@ -16,6 +22,14 @@ export function useAsyncSub(signal, params, options) {
|
|
|
16
22
|
const normalized = normalizeUseSubArgs(signal, params, options);
|
|
17
23
|
return runUseSub(normalized.signal, normalized.params, { ...normalized.options, async: true });
|
|
18
24
|
}
|
|
25
|
+
export function useBatchSub(signal, params, options) {
|
|
26
|
+
const callUseSub = useSub;
|
|
27
|
+
if (arguments.length === 0) {
|
|
28
|
+
return callUseSub(undefined, undefined, { batch: true });
|
|
29
|
+
}
|
|
30
|
+
const normalized = normalizeUseSubArgs(signal, params, options);
|
|
31
|
+
return callUseSub(normalized.signal, normalized.params, { ...normalized.options, async: false, batch: true });
|
|
32
|
+
}
|
|
19
33
|
export default function useSub(signal, params, options) {
|
|
20
34
|
const normalized = normalizeUseSubArgs(signal, params, options);
|
|
21
35
|
return runUseSub(normalized.signal, normalized.params, normalized.options);
|
|
@@ -36,6 +50,8 @@ function isUseSubOptions(value) {
|
|
|
36
50
|
return Object.keys(value).every(key => USE_SUB_OPTION_KEYS.has(key));
|
|
37
51
|
}
|
|
38
52
|
function runUseSub(signal, params, options) {
|
|
53
|
+
if (isBatchBarrierCall(signal, params, options))
|
|
54
|
+
return closeBatchBarrier();
|
|
39
55
|
if (USE_DEFERRED_VALUE) {
|
|
40
56
|
return useSubDeferred(signal, params, options); // eslint-disable-line react-hooks/rules-of-hooks
|
|
41
57
|
}
|
|
@@ -43,6 +59,14 @@ function runUseSub(signal, params, options) {
|
|
|
43
59
|
return useSubClassic(signal, params, options); // eslint-disable-line react-hooks/rules-of-hooks
|
|
44
60
|
}
|
|
45
61
|
}
|
|
62
|
+
function isBatchBarrierCall(signal, params, options) {
|
|
63
|
+
return signal === undefined && params === undefined && !!options?.batch;
|
|
64
|
+
}
|
|
65
|
+
function closeBatchBarrier() {
|
|
66
|
+
const promise = promiseBatcher.getPromiseAll();
|
|
67
|
+
if (promise)
|
|
68
|
+
throw promise;
|
|
69
|
+
}
|
|
46
70
|
// version of sub() which works as a react hook and throws promise for Suspense
|
|
47
71
|
export function useSubDeferred(signal, params, { async = false, defer, batch = false } = {}) {
|
|
48
72
|
const $signalRef = useRef();
|
|
@@ -60,18 +84,20 @@ export function useSubDeferred(signal, params, { async = false, defer, batch = f
|
|
|
60
84
|
// 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
|
|
61
85
|
if (isThenable(promiseOrSignal)) {
|
|
62
86
|
const promise = maybeThrottle(promiseOrSignal);
|
|
87
|
+
const readyPromise = batch ? getBatchReadyPromise(promise) : promise;
|
|
63
88
|
const hasPreviousSignal = !!$signalRef.current;
|
|
64
89
|
if (batch) {
|
|
65
90
|
// Batch suspense must block only on initial load.
|
|
66
91
|
// On resubscribe we keep rendering previous signal and refresh in background.
|
|
67
92
|
if (!hasPreviousSignal) {
|
|
68
93
|
promiseBatcher.add(promise);
|
|
94
|
+
addBatchReadinessCheck(promise);
|
|
69
95
|
}
|
|
70
96
|
else {
|
|
71
|
-
scheduleUpdate(
|
|
97
|
+
scheduleUpdate(readyPromise);
|
|
72
98
|
}
|
|
73
99
|
if (async)
|
|
74
|
-
scheduleUpdate(
|
|
100
|
+
scheduleUpdate(readyPromise);
|
|
75
101
|
return $signalRef.current;
|
|
76
102
|
}
|
|
77
103
|
if (async) {
|
|
@@ -88,6 +114,8 @@ export function useSubDeferred(signal, params, { async = false, defer, batch = f
|
|
|
88
114
|
}
|
|
89
115
|
else {
|
|
90
116
|
const $signal = promiseOrSignal;
|
|
117
|
+
if (batch && !$signalRef.current)
|
|
118
|
+
addBatchReadinessCheckForSignal($signal);
|
|
91
119
|
if ($signalRef.current !== $signal)
|
|
92
120
|
$signalRef.current = $signal;
|
|
93
121
|
return $signal;
|
|
@@ -105,18 +133,20 @@ export function useSubClassic(signal, params, { async = false, batch = false } =
|
|
|
105
133
|
// 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
|
|
106
134
|
if (isThenable(promiseOrSignal)) {
|
|
107
135
|
const promise = maybeThrottle(promiseOrSignal);
|
|
136
|
+
const readyPromise = batch ? getBatchReadyPromise(promise) : promise;
|
|
108
137
|
if (batch) {
|
|
109
138
|
const hasPreviousSignal = cache.has(id);
|
|
110
139
|
// Batch suspense must block only on initial load.
|
|
111
140
|
// On resubscribe we keep rendering previous signal and refresh in background.
|
|
112
141
|
if (!hasPreviousSignal) {
|
|
113
142
|
promiseBatcher.add(promise);
|
|
143
|
+
addBatchReadinessCheck(promise);
|
|
114
144
|
}
|
|
115
145
|
else {
|
|
116
|
-
scheduleUpdate(
|
|
146
|
+
scheduleUpdate(readyPromise);
|
|
117
147
|
}
|
|
118
148
|
if (async)
|
|
119
|
-
scheduleUpdate(
|
|
149
|
+
scheduleUpdate(readyPromise);
|
|
120
150
|
if (hasPreviousSignal)
|
|
121
151
|
return cache.get(id);
|
|
122
152
|
return;
|
|
@@ -143,6 +173,8 @@ export function useSubClassic(signal, params, { async = false, batch = false } =
|
|
|
143
173
|
}
|
|
144
174
|
else {
|
|
145
175
|
const $signal = promiseOrSignal;
|
|
176
|
+
if (batch && !cache.has(id))
|
|
177
|
+
addBatchReadinessCheckForSignal($signal);
|
|
146
178
|
if (cache.get(id) !== $signal) {
|
|
147
179
|
cache.set(id, $signal);
|
|
148
180
|
}
|
|
@@ -178,6 +210,137 @@ function maybeThrottle(promise) {
|
|
|
178
210
|
}, delay);
|
|
179
211
|
});
|
|
180
212
|
}
|
|
213
|
+
function addBatchReadinessCheck(promise) {
|
|
214
|
+
let resolvedSignal;
|
|
215
|
+
let resolved = false;
|
|
216
|
+
promise.then(signal => {
|
|
217
|
+
resolvedSignal = signal;
|
|
218
|
+
resolved = true;
|
|
219
|
+
}, () => {
|
|
220
|
+
resolved = true;
|
|
221
|
+
});
|
|
222
|
+
promiseBatcher.addCheck({
|
|
223
|
+
key: promise,
|
|
224
|
+
type: 'subscription',
|
|
225
|
+
isReady: () => resolved && isBatchSignalReady(resolvedSignal),
|
|
226
|
+
getState: () => getBatchSignalState(resolvedSignal)
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function getBatchReadyPromise(promise) {
|
|
230
|
+
return Promise.resolve(promise).then(async (signal) => {
|
|
231
|
+
await waitForBatchSignalReady(signal);
|
|
232
|
+
return signal;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async function waitForBatchSignalReady(signal) {
|
|
236
|
+
while (!isBatchSignalReady(signal)) {
|
|
237
|
+
await new Promise(resolve => setTimeout(resolve, 16));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function addBatchReadinessCheckForSignal(signal) {
|
|
241
|
+
if (isBatchSignalReady(signal))
|
|
242
|
+
return;
|
|
243
|
+
const promise = Promise.resolve(signal);
|
|
244
|
+
promiseBatcher.add(promise);
|
|
245
|
+
addBatchReadinessCheck(promise);
|
|
246
|
+
}
|
|
247
|
+
function isBatchSignalReady(signal) {
|
|
248
|
+
if (isPublicDocumentSignal(signal)) {
|
|
249
|
+
const $doc = signal;
|
|
250
|
+
return isDocReady($doc[SEGMENTS]);
|
|
251
|
+
}
|
|
252
|
+
if (isQuerySignal(signal))
|
|
253
|
+
return isBatchQueryReady(signal);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
function isBatchQueryReady(signal) {
|
|
257
|
+
const collection = signal[COLLECTION_NAME];
|
|
258
|
+
const params = signal[PARAMS];
|
|
259
|
+
if (!isBatchQueryTransportStable(signal))
|
|
260
|
+
return false;
|
|
261
|
+
const hasExtraResult = isExtraQuery(params);
|
|
262
|
+
if (hasExtraResult)
|
|
263
|
+
return readQueryExtra(signal) !== undefined;
|
|
264
|
+
const isAggregate = !!signal[IS_AGGREGATION] || isAggregationQuery(params);
|
|
265
|
+
const docs = signal.get();
|
|
266
|
+
if (isAggregate) {
|
|
267
|
+
if (Array.isArray(docs))
|
|
268
|
+
return true;
|
|
269
|
+
return readQueryExtra(signal) !== undefined;
|
|
270
|
+
}
|
|
271
|
+
if (!Array.isArray(docs))
|
|
272
|
+
return false;
|
|
273
|
+
materializeQueryDataDocsToCollection(collection, docs);
|
|
274
|
+
const ids = signal.getIds();
|
|
275
|
+
for (const id of ids) {
|
|
276
|
+
if (id == null)
|
|
277
|
+
continue;
|
|
278
|
+
if (!isDocReady([collection, id]))
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
function isBatchQueryTransportStable(signal) {
|
|
284
|
+
const subscriptions = signal[IS_AGGREGATION] ? aggregationSubscriptions : querySubscriptions;
|
|
285
|
+
const entry = subscriptions.entries.get(signal[HASH]);
|
|
286
|
+
if (!entry)
|
|
287
|
+
return false;
|
|
288
|
+
return entry.phase === 'stable' && entry.mode === entry.targetMode;
|
|
289
|
+
}
|
|
290
|
+
function getBatchSignalState(signal) {
|
|
291
|
+
if (isPublicDocumentSignal(signal)) {
|
|
292
|
+
const $doc = signal;
|
|
293
|
+
return {
|
|
294
|
+
kind: 'doc',
|
|
295
|
+
path: $doc[SEGMENTS],
|
|
296
|
+
ready: isDocReady($doc[SEGMENTS])
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (!isQuerySignal(signal))
|
|
300
|
+
return { kind: 'unknown', resolved: !!signal };
|
|
301
|
+
const rootId = getRoot(signal)?.[ROOT_ID];
|
|
302
|
+
const hash = signal[HASH];
|
|
303
|
+
const collection = signal[COLLECTION_NAME];
|
|
304
|
+
return {
|
|
305
|
+
kind: signal[IS_AGGREGATION] ? 'aggregation' : 'query',
|
|
306
|
+
collection,
|
|
307
|
+
hash,
|
|
308
|
+
ids: getPrivateData(rootId, [QUERIES, hash, 'ids'], true),
|
|
309
|
+
hasDocs: Array.isArray(getPrivateData(rootId, [QUERIES, hash, 'docs'], true)),
|
|
310
|
+
hasExtra: getPrivateData(rootId, [QUERIES, hash, 'extra'], true) !== undefined,
|
|
311
|
+
hasAggregation: getPrivateData(rootId, [AGGREGATIONS, hash], true) !== undefined
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function readQueryExtra(signal) {
|
|
315
|
+
try {
|
|
316
|
+
return signal.extra.get();
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
if (isThenable(err))
|
|
320
|
+
return undefined;
|
|
321
|
+
throw err;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function isQuerySignal(signal) {
|
|
325
|
+
return !!signal &&
|
|
326
|
+
(typeof signal === 'object' || typeof signal === 'function') &&
|
|
327
|
+
!!signal[IS_QUERY] &&
|
|
328
|
+
typeof signal[COLLECTION_NAME] === 'string' &&
|
|
329
|
+
typeof signal[HASH] === 'string';
|
|
330
|
+
}
|
|
331
|
+
function isExtraQuery(query) {
|
|
332
|
+
if (!query || typeof query !== 'object')
|
|
333
|
+
return false;
|
|
334
|
+
return !!(query.$count ||
|
|
335
|
+
query.$queryName ||
|
|
336
|
+
query.$aggregationName);
|
|
337
|
+
}
|
|
338
|
+
function isAggregationQuery(query) {
|
|
339
|
+
if (!query || typeof query !== 'object')
|
|
340
|
+
return false;
|
|
341
|
+
return !!(query.$aggregate ||
|
|
342
|
+
query.$aggregationName);
|
|
343
|
+
}
|
|
181
344
|
function isThenable(value) {
|
|
182
345
|
return !!value &&
|
|
183
346
|
(typeof value === 'object' || typeof value === 'function') &&
|
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.18",
|
|
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": "67f9d6addabfd46c8d6459670cfb95e0b5653ace"
|
|
138
138
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export const useBatch: any
|
|
2
|
-
export const useBatchDoc: any
|
|
3
|
-
export const useBatchDoc$: any
|
|
4
|
-
export const useBatchQuery: any
|
|
5
|
-
export const useBatchQuery$: any
|
|
6
|
-
export const useBatchQueryIds: any
|
|
7
|
-
export const useBatchQueryDoc: any
|
|
8
|
-
export const useBatchQueryDoc$: any
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { getRootSignal, GLOBAL_ROOT_ID } from "../Root.js";
|
|
2
|
-
import useSub from "../../react/useSub.js";
|
|
3
|
-
import universal$ from "../../react/universal$.js";
|
|
4
|
-
import * as promiseBatcher from "../../react/promiseBatcher.js";
|
|
5
|
-
import { isQueryReady } from './queryReadiness.js';
|
|
6
|
-
const $root = getRootSignal({ rootId: GLOBAL_ROOT_ID, rootFunction: universal$ });
|
|
7
|
-
const emittedCompatWarnings = new Set();
|
|
8
|
-
export function useBatch() {
|
|
9
|
-
const promise = promiseBatcher.getPromiseAll();
|
|
10
|
-
if (promise)
|
|
11
|
-
throw promise;
|
|
12
|
-
}
|
|
13
|
-
export function useBatchDoc(collection, id, options) {
|
|
14
|
-
const $doc = useBatchDoc$(collection, id, options);
|
|
15
|
-
if (!$doc)
|
|
16
|
-
return [undefined, undefined];
|
|
17
|
-
return [$doc.get(), $doc];
|
|
18
|
-
}
|
|
19
|
-
export function useBatchDoc$(collection, id, _options) {
|
|
20
|
-
const $doc = getDocSignal(collection, id, 'useBatchDoc');
|
|
21
|
-
const options = _options ? { ..._options, ...BATCH_SUB_OPTIONS } : BATCH_SUB_OPTIONS;
|
|
22
|
-
return useSub($doc, undefined, options);
|
|
23
|
-
}
|
|
24
|
-
function useSubscribedQuery(collection, query, options, hookName, subscribe) {
|
|
25
|
-
const normalizedQuery = normalizeQuery(query, hookName);
|
|
26
|
-
const $collection = getCollectionSignal(collection, query, hookName);
|
|
27
|
-
const $query = subscribe($collection, normalizedQuery, options);
|
|
28
|
-
return {
|
|
29
|
-
normalizedQuery,
|
|
30
|
-
$collection,
|
|
31
|
-
$query: getExtraQuerySignal($query, normalizedQuery)
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function getExtraQuerySignal($query, normalizedQuery) {
|
|
35
|
-
if (!$query)
|
|
36
|
-
return $query;
|
|
37
|
-
return isExtraQuery(normalizedQuery) ? $query.extra : $query;
|
|
38
|
-
}
|
|
39
|
-
export function useBatchQuery$(collection, query, _options) {
|
|
40
|
-
const options = normalizeBatchSubOptions(_options);
|
|
41
|
-
const { $query } = useSubscribedQuery(collection, query, options, 'useBatchQuery', useSub);
|
|
42
|
-
return $query;
|
|
43
|
-
}
|
|
44
|
-
export function useBatchQuery(collection, query, _options) {
|
|
45
|
-
const options = normalizeBatchSubOptions(_options);
|
|
46
|
-
const { $collection, $query } = useSubscribedQuery(collection, query, options, 'useBatchQuery', useSub);
|
|
47
|
-
if (!$query)
|
|
48
|
-
return [undefined, $collection];
|
|
49
|
-
return [$query.get(), $collection];
|
|
50
|
-
}
|
|
51
|
-
export function useBatchQueryIds(collection, ids = [], options = {}) {
|
|
52
|
-
const list = Array.isArray(ids) ? ids.slice() : [];
|
|
53
|
-
if (options?.reverse)
|
|
54
|
-
list.reverse();
|
|
55
|
-
const [docs, $collection] = useBatchQuery(collection, { _id: { $in: list } }, options);
|
|
56
|
-
if (!docs)
|
|
57
|
-
return [docs, $collection];
|
|
58
|
-
const docsById = new Map();
|
|
59
|
-
for (const doc of docs) {
|
|
60
|
-
const id = doc?._id ?? doc?.id;
|
|
61
|
-
if (id != null)
|
|
62
|
-
docsById.set(id, doc);
|
|
63
|
-
}
|
|
64
|
-
const items = list.map(id => docsById.get(id)).filter(Boolean);
|
|
65
|
-
return [items, $collection];
|
|
66
|
-
}
|
|
67
|
-
export function useBatchQueryDoc(collection, query, options) {
|
|
68
|
-
const normalized = normalizeQuery(query, 'useBatchQueryDoc');
|
|
69
|
-
const queryDoc = {
|
|
70
|
-
...normalized,
|
|
71
|
-
$limit: 1,
|
|
72
|
-
$sort: normalized.$sort || { createdAt: -1 }
|
|
73
|
-
};
|
|
74
|
-
const [docs, $collection] = useBatchQuery(collection, queryDoc, options);
|
|
75
|
-
if (!docs)
|
|
76
|
-
return [undefined, undefined];
|
|
77
|
-
const doc = docs && docs[0];
|
|
78
|
-
const docId = doc?._id ?? doc?.id;
|
|
79
|
-
const $doc = docId != null ? $collection[docId] : undefined;
|
|
80
|
-
return [doc, $doc];
|
|
81
|
-
}
|
|
82
|
-
export function useBatchQueryDoc$(collection, query, options) {
|
|
83
|
-
const [, $doc] = useBatchQueryDoc(collection, query, options);
|
|
84
|
-
return $doc;
|
|
85
|
-
}
|
|
86
|
-
function getDocSignal(collection, id, hookName) {
|
|
87
|
-
if (typeof collection !== 'string') {
|
|
88
|
-
throw Error(`[${hookName}] collection must be a string. Got: ${collection}`);
|
|
89
|
-
}
|
|
90
|
-
if (id == null) {
|
|
91
|
-
warnCompatOnce(`doc:${hookName}:${collection}:${id}`, `
|
|
92
|
-
[${hookName}] You are trying to subscribe to an undefined document id:
|
|
93
|
-
${collection}.${id}
|
|
94
|
-
Falling back to '__NULL__' document to prevent critical crash.
|
|
95
|
-
You should prevent situations when the \`id\` is undefined.
|
|
96
|
-
`);
|
|
97
|
-
id = '__NULL__';
|
|
98
|
-
}
|
|
99
|
-
return $root[collection][id];
|
|
100
|
-
}
|
|
101
|
-
function getCollectionSignal(collection, query, hookName) {
|
|
102
|
-
if (typeof collection !== 'string') {
|
|
103
|
-
throw Error(`[${hookName}] collection must be a string. Got: ${collection}`);
|
|
104
|
-
}
|
|
105
|
-
if (query == null) {
|
|
106
|
-
warnCompatOnce(`query:${hookName}:${collection}`, `
|
|
107
|
-
[${hookName}] Query is undefined. Got:
|
|
108
|
-
${collection}, ${query}
|
|
109
|
-
Falling back to {_id: '__NON_EXISTENT__'} query to prevent critical crash.
|
|
110
|
-
You should prevent situations when the \`query\` is undefined.
|
|
111
|
-
`);
|
|
112
|
-
}
|
|
113
|
-
return $root[collection];
|
|
114
|
-
}
|
|
115
|
-
function warnCompatOnce(key, message) {
|
|
116
|
-
if (emittedCompatWarnings.has(key))
|
|
117
|
-
return;
|
|
118
|
-
emittedCompatWarnings.add(key);
|
|
119
|
-
console.warn(message);
|
|
120
|
-
}
|
|
121
|
-
export function __resetCompatWarningsForTests() {
|
|
122
|
-
emittedCompatWarnings.clear();
|
|
123
|
-
}
|
|
124
|
-
function normalizeQuery(query, hookName) {
|
|
125
|
-
if (query == null)
|
|
126
|
-
return { _id: '__NON_EXISTENT__' };
|
|
127
|
-
if (typeof query !== 'object') {
|
|
128
|
-
throw Error(`[${hookName}] query must be an object. Got: ${query}`);
|
|
129
|
-
}
|
|
130
|
-
return query;
|
|
131
|
-
}
|
|
132
|
-
function isExtraQuery(query) {
|
|
133
|
-
if (!query || typeof query !== 'object')
|
|
134
|
-
return false;
|
|
135
|
-
return !!(query.$count ||
|
|
136
|
-
query.$queryName ||
|
|
137
|
-
query.$aggregationName);
|
|
138
|
-
}
|
|
139
|
-
const BATCH_SUB_OPTIONS = Object.freeze({
|
|
140
|
-
async: false,
|
|
141
|
-
batch: true,
|
|
142
|
-
// Batch hooks are a hard suspense barrier. Deferred params can skip the barrier
|
|
143
|
-
// on route transitions and cause immediate reads from stale/empty local nodes.
|
|
144
|
-
defer: false
|
|
145
|
-
});
|
|
146
|
-
function normalizeBatchSubOptions(options) {
|
|
147
|
-
return options ? { ...options, ...BATCH_SUB_OPTIONS } : BATCH_SUB_OPTIONS;
|
|
148
|
-
}
|
|
149
|
-
export const __COMPAT_BATCH_READY__ = {
|
|
150
|
-
isQueryReady
|
|
151
|
-
};
|