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 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
- export function isQueryReady(collection: any, idsSegments: any, docsSegments: any, extraSegments: any, aggregationSegments: any, isAggregate: any, hasExtraResult: any): boolean;
2
- export function isDocReady(segments: any): boolean;
3
- export function waitForImperativeQueryReady($query: any): Promise<void>;
4
- export function __setImperativeQueryReadyTimeoutForTests(timeoutMs: any): void;
5
- export function __resetImperativeQueryReadyTimeoutForTests(): void;
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
@@ -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
- export function getPrivateDataRoot(rootId: any, create?: boolean): {
2
- [x: string]: unknown;
3
- [x: number]: unknown;
4
- };
5
- export function getPrivateDataRawRoot(rootId: any, create?: boolean): {
6
- [x: string]: unknown;
7
- [x: number]: unknown;
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] useBatch() is waiting for data materialization checks.', details);
98
+ console.warn('[teamplay] useBatchSub() is waiting for data materialization checks.', details);
99
99
  }
100
100
  function isDevMode() {
101
101
  const processLike = globalThis.process;
@@ -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] useBatch* hooks were used without a closing useBatch() call.');
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
  }
@@ -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.
@@ -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(promise);
97
+ scheduleUpdate(readyPromise);
72
98
  }
73
99
  if (async)
74
- scheduleUpdate(promise);
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(promise);
146
+ scheduleUpdate(readyPromise);
117
147
  }
118
148
  if (async)
119
- scheduleUpdate(promise);
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.17",
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": "26d5ef765255ec6a772718e50439dd3202924d3c"
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
- };