teamplay 0.5.0-alpha.17 → 0.5.0-alpha.19

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";
@@ -21,7 +21,7 @@ import disposeRootContext from "../disposeRootContext.js";
21
21
  import { arrayInsertPrivateData, arrayMovePrivateData, arrayPopPrivateData, arrayPushPrivateData, arrayRemovePrivateData, arrayShiftPrivateData, arrayUnshiftPrivateData, delPrivateData, setReplacePrivateData, stringInsertPrivateData, stringRemovePrivateData } from '../privateData.js';
22
22
  class SignalCompat extends Signal {
23
23
  static ID_FIELDS = ['_id', 'id'];
24
- static [GETTERS] = [...DEFAULT_GETTERS, 'getCopy', 'getDeepCopy'];
24
+ static [GETTERS] = DEFAULT_GETTERS;
25
25
  path() {
26
26
  if (arguments.length > 0)
27
27
  throw Error('Signal.path() does not accept any arguments');
@@ -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
@@ -54,6 +54,12 @@ export declare class Signal<TValue = unknown> extends Function {
54
54
  get(): TValue;
55
55
  /** Return document ids for a query or aggregation signal. */
56
56
  getIds(): string[];
57
+ /** Return query extra data, aggregation data, or undefined for ordinary signals. */
58
+ getExtra(): unknown;
59
+ /** Return a shallow copy of the current value. */
60
+ getCopy(): TValue;
61
+ /** Return a deep copy of the current value. */
62
+ getDeepCopy(): TValue;
57
63
  /** Read the current value without tracking it for reactive rendering. */
58
64
  peek(): TValue;
59
65
  /** Return the document id represented by this document signal. */
@@ -105,6 +111,18 @@ export declare class Signal<TValue = unknown> extends Function {
105
111
  * @param value New value to store at this signal path.
106
112
  */
107
113
  setReplace(value: TValue): Promise<void>;
114
+ /** Set the current value only when it is null or undefined. */
115
+ setNull(value: TValue): Promise<void>;
116
+ /** Replace the current value unless it is exactly equal to the new value. */
117
+ setDiff(value: TValue): Promise<void>;
118
+ /** Recursively diff objects and arrays at the current signal path. */
119
+ setDiffDeep(value: TValue): Promise<void>;
120
+ /**
121
+ * Set multiple object fields with per-key replace semantics.
122
+ * Unlike assign(), null is stored as null and undefined follows setReplace() semantics.
123
+ * @param object Object containing fields to set.
124
+ */
125
+ setEach(object: NonNullable<TValue> extends object ? Partial<NonNullable<TValue>> : never): Promise<void>;
108
126
  /**
109
127
  * Set multiple object fields at once. Fields set to `null` or `undefined` are deleted.
110
128
  * @param value Object containing fields to set or delete.
@@ -12,15 +12,17 @@
12
12
  * 3. If extremely late bindings are enabled, to prevent name collisions when accessing fields
13
13
  * in the raw data tree which have the same name as signal's methods
14
14
  */
15
+ import { raw } from '@nx-js/observer-util';
16
+ import arrayDiff from 'arraydiff';
15
17
  import uuid from '@teamplay/utils/uuid';
16
18
  import { get as _get, setPublicDoc as _setPublicDoc, setPublicDocReplace as _setPublicDocReplace, del as _del, dataTreeRaw, getRaw, getLogicalRootSnapshot, incrementPublic as _incrementPublic, arrayPushPublic as _arrayPushPublic, arrayUnshiftPublic as _arrayUnshiftPublic, arrayInsertPublic as _arrayInsertPublic, arrayPopPublic as _arrayPopPublic, arrayShiftPublic as _arrayShiftPublic, arrayRemovePublic as _arrayRemovePublic, arrayMovePublic as _arrayMovePublic, stringInsertPublic as _stringInsertPublic, stringRemovePublic as _stringRemovePublic } from './dataTree.js';
17
19
  import getSignal, { rawSignal } from "./getSignal.js";
18
20
  import { docSubscriptions } from './Doc.js';
19
21
  import { IS_QUERY, HASH, QUERIES } from './Query.js';
20
- import { AGGREGATIONS, getAggregationCollectionName, getAggregationDocId } from './Aggregation.js';
22
+ import { AGGREGATIONS, IS_AGGREGATION, getAggregationCollectionName, getAggregationDocId } from './Aggregation.js';
21
23
  import { ROOT_FUNCTION, ROOT_ID, getRoot } from "./Root.js";
22
24
  import { isPrivateMutationForbidden } from "./connection.js";
23
- import { DEFAULT_ID_FIELDS, getIdFieldsForSegments, isIdFieldPath, isPublicDocPath, normalizeIdFields, prepareAddPayload, resolveAddDocId } from "./idFields.js";
25
+ import { DEFAULT_ID_FIELDS, getIdFieldsForSegments, isIdFieldPath, isPlainObject, isPublicDocPath, normalizeIdFields, prepareAddPayload, resolveAddDocId } from "./idFields.js";
24
26
  import { isCompatEnv } from './compatEnv.js';
25
27
  import { resolveRefSegmentsSafe, resolveRefSignalSafe } from './Compat/refFallback.js';
26
28
  import { compatStartOnRoot, compatStopOnRoot, joinScopePath } from './Compat/startStopCompat.js';
@@ -202,6 +204,28 @@ export class Signal extends Function {
202
204
  throw Error('Signal.getIds() does not accept any arguments');
203
205
  return getSignalIds(this, SIGNAL_READ_CONTEXT);
204
206
  }
207
+ /** Return query extra data, aggregation data, or undefined for ordinary signals. */
208
+ getExtra() {
209
+ if (arguments.length > 0)
210
+ throw Error('Signal.getExtra() does not accept any arguments');
211
+ if (this[IS_AGGREGATION])
212
+ return this.get();
213
+ if (this[IS_QUERY])
214
+ return this.extra.get();
215
+ return undefined;
216
+ }
217
+ /** Return a shallow copy of the current value. */
218
+ getCopy() {
219
+ if (arguments.length > 0)
220
+ throw Error('Signal.getCopy() does not accept any arguments');
221
+ return shallowCopy(this.get());
222
+ }
223
+ /** Return a deep copy of the current value. */
224
+ getDeepCopy() {
225
+ if (arguments.length > 0)
226
+ throw Error('Signal.getDeepCopy() does not accept any arguments');
227
+ return deepCopy(this.get());
228
+ }
205
229
  /** Read the current value without tracking it for reactive rendering. */
206
230
  peek() {
207
231
  if (arguments.length > 0)
@@ -292,6 +316,50 @@ export class Signal extends Function {
292
316
  }
293
317
  setReplacePrivateData(getSignalOwningRootId(this), segments, nextValue);
294
318
  }
319
+ /** Set the current value only when it is null or undefined. */
320
+ async setNull(value) {
321
+ if (arguments.length > 1)
322
+ throw Error('Signal.setNull() expects a single argument');
323
+ if (this.get() != null)
324
+ return;
325
+ await this.setReplace(value);
326
+ }
327
+ /** Replace the current value unless it is exactly equal to the new value. */
328
+ async setDiff(value) {
329
+ if (arguments.length > 1)
330
+ throw Error('Signal.setDiff() expects a single argument');
331
+ const before = this.peek();
332
+ if (racerEqual(before, value))
333
+ return;
334
+ await this.setReplace(value);
335
+ }
336
+ /** Recursively diff objects and arrays at the current signal path. */
337
+ async setDiffDeep(value) {
338
+ if (arguments.length > 1)
339
+ throw Error('Signal.setDiffDeep() expects a single argument');
340
+ await runInBatch(() => setDiffDeepOnSignal(this, value));
341
+ }
342
+ /**
343
+ * Set multiple object fields with per-key replace semantics.
344
+ * Unlike assign(), null is stored as null and undefined follows setReplace() semantics.
345
+ * @param object Object containing fields to set.
346
+ */
347
+ async setEach(object) {
348
+ if (arguments.length > 1)
349
+ throw Error('Signal.setEach() expects a single argument');
350
+ if (!object)
351
+ return;
352
+ if (typeof object !== 'object') {
353
+ throw Error('Signal.setEach() expects an object argument, got: ' + typeof object);
354
+ }
355
+ await runInBatch(async () => {
356
+ const promises = [];
357
+ for (const key of Object.keys(object)) {
358
+ promises.push(this[key].setReplace(object[key]));
359
+ }
360
+ await Promise.all(promises);
361
+ });
362
+ }
295
363
  /**
296
364
  * Set multiple object fields at once. Fields set to `null` or `undefined` are deleted.
297
365
  * @param value Object containing fields to set or delete.
@@ -522,6 +590,183 @@ export class Signal extends Function {
522
590
  await deleteSignalValue(this, SIGNAL_VALUE_MUTATION_CONTEXT);
523
591
  }
524
592
  }
593
+ async function setDiffDeepOnSignal($target, value) {
594
+ if ($target[SEGMENTS].length === 0)
595
+ throw Error('Can\'t set the root signal data');
596
+ await diffDeepOnSignal($target, $target.peek(), value);
597
+ }
598
+ async function diffDeepOnSignal($signal, before, after) {
599
+ if (before === after)
600
+ return;
601
+ if (Array.isArray(before) && Array.isArray(after)) {
602
+ const diff = arrayDiff(before, after, deepEqual);
603
+ if (!diff.length)
604
+ return;
605
+ const index = getSingleArrayReplacementIndex(diff);
606
+ if (index != null) {
607
+ await diffDeepOnSignal(getChildSignal($signal, index), before[index], after[index]);
608
+ return;
609
+ }
610
+ await applyArrayDiff($signal, diff);
611
+ return;
612
+ }
613
+ if (isDiffableObject(before, after)) {
614
+ const preservePath = $signal[SEGMENTS];
615
+ for (const key of Object.keys(before)) {
616
+ if (Object.prototype.hasOwnProperty.call(after, key))
617
+ continue;
618
+ await deleteForDiffDeep(getChildSignal($signal, key), preservePath);
619
+ }
620
+ for (const key of Object.keys(after)) {
621
+ await diffDeepOnSignal(getChildSignal($signal, key), before[key], after[key]);
622
+ }
623
+ return;
624
+ }
625
+ await $signal.setReplace(after);
626
+ }
627
+ function isDiffableObject(before, after) {
628
+ if (!isPlainObject(before) || !isPlainObject(after))
629
+ return false;
630
+ if (isReactLike(before) || isReactLike(after))
631
+ return false;
632
+ return true;
633
+ }
634
+ function isReactLike(value) {
635
+ return !!(value && typeof value === 'object' && typeof value.$$typeof === 'symbol');
636
+ }
637
+ function getSingleArrayReplacementIndex(diff) {
638
+ if (!Array.isArray(diff) || diff.length !== 2)
639
+ return null;
640
+ const first = diff[0];
641
+ const second = diff[1];
642
+ if (first instanceof arrayDiff.RemoveDiff &&
643
+ second instanceof arrayDiff.InsertDiff &&
644
+ first.index === second.index &&
645
+ first.howMany === 1 &&
646
+ second.values.length === 1) {
647
+ return first.index;
648
+ }
649
+ return null;
650
+ }
651
+ async function applyArrayDiff($signal, diff) {
652
+ for (const item of diff) {
653
+ if (item instanceof arrayDiff.InsertDiff) {
654
+ await $signal.insert(item.index, item.values);
655
+ continue;
656
+ }
657
+ if (item instanceof arrayDiff.RemoveDiff) {
658
+ await $signal.remove(item.index, item.howMany);
659
+ continue;
660
+ }
661
+ if (item instanceof arrayDiff.MoveDiff) {
662
+ await $signal.move(item.from, item.to, item.howMany);
663
+ }
664
+ }
665
+ }
666
+ async function deleteForDiffDeep($signal, preservePath) {
667
+ const segments = $signal[SEGMENTS];
668
+ const idFields = getIdFieldsForSegments(segments);
669
+ if (isIdFieldPath(segments, idFields))
670
+ return;
671
+ if (isPublicCollection(segments[0])) {
672
+ await $signal.del();
673
+ return;
674
+ }
675
+ if (isPrivateMutationForbidden()) {
676
+ throw Error(`
677
+ Can't modify private collections data when 'publicOnly' is enabled.
678
+ On the server you can only work with public collections.
679
+ `);
680
+ }
681
+ delPrivateData(getSignalOwningRootId($signal), segments, { preservePath });
682
+ }
683
+ function getChildSignal($parent, key) {
684
+ return getSignal(getRoot($parent) || $parent, [...$parent[SEGMENTS], key]);
685
+ }
686
+ function deepEqual(left, right) {
687
+ if (left === right)
688
+ return true;
689
+ if (left == null || right == null)
690
+ return false;
691
+ if (typeof left !== 'object' || typeof right !== 'object')
692
+ return false;
693
+ if (Array.isArray(left) !== Array.isArray(right))
694
+ return false;
695
+ if (Array.isArray(left)) {
696
+ if (left.length !== right.length)
697
+ return false;
698
+ for (let i = 0; i < left.length; i++) {
699
+ if (!deepEqual(left[i], right[i]))
700
+ return false;
701
+ }
702
+ return true;
703
+ }
704
+ if (!isPlainObject(left) || !isPlainObject(right))
705
+ return false;
706
+ const leftKeys = Object.keys(left);
707
+ const rightKeys = Object.keys(right);
708
+ if (leftKeys.length !== rightKeys.length)
709
+ return false;
710
+ for (const key of leftKeys) {
711
+ if (!Object.prototype.hasOwnProperty.call(right, key))
712
+ return false;
713
+ if (!deepEqual(left[key], right[key]))
714
+ return false;
715
+ }
716
+ return true;
717
+ }
718
+ function racerEqual(left, right) {
719
+ return left === right || (Number.isNaN(left) && Number.isNaN(right));
720
+ }
721
+ function shallowCopy(value) {
722
+ const rawValue = raw(value);
723
+ if (Array.isArray(rawValue))
724
+ return rawValue.slice();
725
+ if (rawValue && typeof rawValue === 'object')
726
+ return { ...rawValue };
727
+ return rawValue;
728
+ }
729
+ function deepCopy(value) {
730
+ const rawValue = raw(value);
731
+ if (!rawValue || typeof rawValue !== 'object')
732
+ return rawValue;
733
+ if (typeof globalThis.structuredClone === 'function') {
734
+ try {
735
+ return globalThis.structuredClone(rawValue);
736
+ }
737
+ catch { }
738
+ }
739
+ return racerDeepCopy(rawValue);
740
+ }
741
+ // Racer-style deep copy:
742
+ // - Preserves prototypes by instantiating via `new value.constructor()`
743
+ // - Copies own enumerable props recursively
744
+ // - Keeps functions as-is (no cloning)
745
+ // - Handles Date by creating a new Date
746
+ // Limitations: does not handle cyclic refs, Map/Set/RegExp/TypedArray, non-enumerables.
747
+ function racerDeepCopy(value) {
748
+ if (value instanceof Date)
749
+ return new Date(value);
750
+ if (typeof value === 'object') {
751
+ if (value === null)
752
+ return null;
753
+ if (Array.isArray(value)) {
754
+ const array = [];
755
+ for (let i = value.length; i--;) {
756
+ array[i] = racerDeepCopy(value[i]);
757
+ }
758
+ return array;
759
+ }
760
+ const object = new value.constructor();
761
+ for (const key in value) {
762
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
763
+ object[key] = racerDeepCopy(value[key]);
764
+ }
765
+ }
766
+ return object;
767
+ }
768
+ return value;
769
+ }
525
770
  // dot syntax returns a child signal only if no such method or property exists
526
771
  export const regularBindings = {
527
772
  apply(signal, thisArg, argumentsList) {
@@ -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
@@ -2,4 +2,4 @@ export const SEGMENTS = Symbol('path segments targeting the particular node in t
2
2
  export const ARRAY_METHOD = Symbol('run array method on the signal');
3
3
  export const GET = Symbol('get the value of the signal - either observed or raw');
4
4
  export const GETTERS = Symbol('get the list of this signal\'s getters');
5
- export const DEFAULT_GETTERS = ['path', 'root', 'id', 'get', 'peek', 'getId', 'map', 'reduce', 'find', 'getIds', 'getExtra', 'getCollection'];
5
+ export const DEFAULT_GETTERS = ['path', 'root', 'id', 'get', 'peek', 'getId', 'map', 'reduce', 'find', 'getIds', 'getExtra', 'getCopy', 'getDeepCopy', 'getCollection'];
@@ -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.19",
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": "4bafa99265d8aed36e952d89d8d0ed0082748ad5"
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
- };