teamplay 0.5.0-alpha.24 → 0.5.0-alpha.26
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 +2 -1
- package/dist/index.js +1 -1
- package/dist/orm/dataTree.js +36 -30
- package/dist/orm/sub.d.ts +12 -7
- package/dist/orm/sub.js +87 -30
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -53,7 +53,8 @@ export { default as signal } from './orm/getSignal.js';
|
|
|
53
53
|
export { GLOBAL_ROOT_ID } from './orm/Root.js';
|
|
54
54
|
export declare const $: RootSignal;
|
|
55
55
|
export default $;
|
|
56
|
-
export { default as sub } from './orm/sub.js';
|
|
56
|
+
export { default as sub, unsub } from './orm/sub.js';
|
|
57
|
+
export type { SubMode, SubOptions } from './orm/sub.js';
|
|
57
58
|
export { default as useSub, useAsyncSub, useBatchSub, setUseDeferredValue as __setUseDeferredValue, setDefaultDefer as __setDefaultDefer } from './react/useSub.js';
|
|
58
59
|
export { default as useSuspendMemo, useSuspendMemoByKey } from './react/useSuspendMemo.js';
|
|
59
60
|
export declare const observer: ObserverFunction;
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ export { GLOBAL_ROOT_ID } from "./orm/Root.js";
|
|
|
14
14
|
const getRuntimeRootSignal = _getRootSignal;
|
|
15
15
|
export const $ = getRuntimeRootSignal({ rootId: GLOBAL_ROOT_ID, rootFunction: universal$ });
|
|
16
16
|
export default $;
|
|
17
|
-
export { default as sub } from "./orm/sub.js";
|
|
17
|
+
export { default as sub, unsub } from "./orm/sub.js";
|
|
18
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;
|
package/dist/orm/dataTree.js
CHANGED
|
@@ -178,14 +178,14 @@ export async function setPublicDoc(segments, value, deleteValue = false) {
|
|
|
178
178
|
if (isIdFieldPath(segments, idFields))
|
|
179
179
|
return;
|
|
180
180
|
const doc = getConnection().get(collection, docId);
|
|
181
|
-
let docState = resolvePublicDocState({ collection, docId, doc, idFields,
|
|
181
|
+
let docState = resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDataFromLocal: true });
|
|
182
182
|
if (!docState.exists && segments.length > 2) {
|
|
183
|
-
docState = await
|
|
183
|
+
docState = await resolvePublicDocStateWithFetchFallback({
|
|
184
184
|
collection,
|
|
185
185
|
docId,
|
|
186
186
|
doc,
|
|
187
187
|
idFields,
|
|
188
|
-
|
|
188
|
+
hydrateDocDataFromLocal: true
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
191
|
if (!docState.exists && deleteValue)
|
|
@@ -286,14 +286,14 @@ export async function setPublicDocReplace(segments, value) {
|
|
|
286
286
|
if (isIdFieldPath(segments, idFields))
|
|
287
287
|
return;
|
|
288
288
|
const doc = getConnection().get(collection, docId);
|
|
289
|
-
let docState = resolvePublicDocState({ collection, docId, doc, idFields,
|
|
289
|
+
let docState = resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDataFromLocal: true });
|
|
290
290
|
if (!docState.exists && segments.length > 2) {
|
|
291
|
-
docState = await
|
|
291
|
+
docState = await resolvePublicDocStateWithFetchFallback({
|
|
292
292
|
collection,
|
|
293
293
|
docId,
|
|
294
294
|
doc,
|
|
295
295
|
idFields,
|
|
296
|
-
|
|
296
|
+
hydrateDocDataFromLocal: true
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
299
|
// make sure that the value is not observable to not trigger extra reads. And clone it
|
|
@@ -374,9 +374,10 @@ async function createPublicDocAndHydrateLocal({ doc, collection, docId, newDoc,
|
|
|
374
374
|
await new Promise((resolve, reject) => {
|
|
375
375
|
doc.create(newDoc, err => err ? reject(err) : resolve());
|
|
376
376
|
});
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
377
|
+
// Keep public creates immediately writable even when the ShareDB snapshot has
|
|
378
|
+
// not been loaded via subscribe/fetch yet. UI flows rely on optimistic add()
|
|
379
|
+
// followed by child writes without waiting for DB confirmation.
|
|
380
|
+
if (doc?.data == null) {
|
|
380
381
|
const localDoc = JSON.parse(JSON.stringify(newDoc || {}));
|
|
381
382
|
if (isPlainObject(localDoc))
|
|
382
383
|
injectIdFields(localDoc, idFields, docId);
|
|
@@ -387,7 +388,7 @@ async function createPublicDocAndHydrateLocal({ doc, collection, docId, newDoc,
|
|
|
387
388
|
}
|
|
388
389
|
ensureLocalDocSyncedWithShareDoc({ collection, docId, doc, idFields });
|
|
389
390
|
}
|
|
390
|
-
function resolvePublicDocState({ collection, docId, doc, idFields,
|
|
391
|
+
function resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDataFromLocal = false }) {
|
|
391
392
|
ensureLocalDocSyncedWithShareDoc({ collection, docId, doc, idFields });
|
|
392
393
|
if (isMissingShareDoc(doc)) {
|
|
393
394
|
return { exists: false, snapshot: undefined, source: 'none' };
|
|
@@ -400,24 +401,29 @@ function resolvePublicDocState({ collection, docId, doc, idFields, hydrateCompat
|
|
|
400
401
|
};
|
|
401
402
|
}
|
|
402
403
|
const localSnapshot = getRaw([collection, docId]);
|
|
403
|
-
if (
|
|
404
|
+
if (localSnapshot == null) {
|
|
404
405
|
return { exists: false, snapshot: undefined, source: 'none' };
|
|
405
406
|
}
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
if (
|
|
407
|
+
// Local raw data can be the source of truth between add() and later subpath
|
|
408
|
+
// mutations even if ShareDB doc.data is currently empty or was recreated.
|
|
409
|
+
if (hydrateDocDataFromLocal) {
|
|
409
410
|
doc.data = localSnapshot;
|
|
410
411
|
}
|
|
411
412
|
return { exists: true, snapshot: localSnapshot, source: 'local' };
|
|
412
413
|
}
|
|
413
|
-
async function
|
|
414
|
-
let docState = resolvePublicDocState({ collection, docId, doc, idFields,
|
|
415
|
-
if (docState.exists
|
|
414
|
+
async function resolvePublicDocStateWithFetchFallback({ collection, docId, doc, idFields, hydrateDocDataFromLocal = false }) {
|
|
415
|
+
let docState = resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDataFromLocal });
|
|
416
|
+
if (docState.exists)
|
|
417
|
+
return docState;
|
|
418
|
+
const shouldFetch = isCompatEnv() || (getRaw([collection, docId]) != null &&
|
|
419
|
+
isMissingShareDoc(doc) &&
|
|
420
|
+
doc.version == null);
|
|
421
|
+
if (!shouldFetch)
|
|
416
422
|
return docState;
|
|
417
423
|
await new Promise((resolve, reject) => {
|
|
418
424
|
doc.fetch(err => err ? reject(err) : resolve());
|
|
419
425
|
});
|
|
420
|
-
docState = resolvePublicDocState({ collection, docId, doc, idFields,
|
|
426
|
+
docState = resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDataFromLocal });
|
|
421
427
|
return docState;
|
|
422
428
|
}
|
|
423
429
|
function ensureLocalDocSyncedWithShareDoc({ collection, docId, doc, idFields }) {
|
|
@@ -572,12 +578,12 @@ export async function incrementPublic(segments, byNumber) {
|
|
|
572
578
|
throw Error(ERRORS.publicDoc(segments));
|
|
573
579
|
const doc = getConnection().get(collection, docId);
|
|
574
580
|
const idFields = getIdFieldsForSegments([collection, docId]);
|
|
575
|
-
const docState = await
|
|
581
|
+
const docState = await resolvePublicDocStateWithFetchFallback({
|
|
576
582
|
collection,
|
|
577
583
|
docId,
|
|
578
584
|
doc,
|
|
579
585
|
idFields,
|
|
580
|
-
|
|
586
|
+
hydrateDocDataFromLocal: true
|
|
581
587
|
});
|
|
582
588
|
if (!docState.exists)
|
|
583
589
|
throw Error(ERRORS.nonExistingDoc(segments));
|
|
@@ -615,12 +621,12 @@ export async function arrayInsertPublic(segments, index, values) {
|
|
|
615
621
|
throw Error(ERRORS.publicDoc(segments));
|
|
616
622
|
const doc = getConnection().get(collection, docId);
|
|
617
623
|
const idFields = getIdFieldsForSegments([collection, docId]);
|
|
618
|
-
const docState = await
|
|
624
|
+
const docState = await resolvePublicDocStateWithFetchFallback({
|
|
619
625
|
collection,
|
|
620
626
|
docId,
|
|
621
627
|
doc,
|
|
622
628
|
idFields,
|
|
623
|
-
|
|
629
|
+
hydrateDocDataFromLocal: true
|
|
624
630
|
});
|
|
625
631
|
if (!docState.exists)
|
|
626
632
|
throw Error(ERRORS.nonExistingDoc(segments));
|
|
@@ -674,12 +680,12 @@ export async function arrayRemovePublic(segments, index, howMany = 1) {
|
|
|
674
680
|
throw Error(ERRORS.publicDoc(segments));
|
|
675
681
|
const doc = getConnection().get(collection, docId);
|
|
676
682
|
const idFields = getIdFieldsForSegments([collection, docId]);
|
|
677
|
-
const docState = await
|
|
683
|
+
const docState = await resolvePublicDocStateWithFetchFallback({
|
|
678
684
|
collection,
|
|
679
685
|
docId,
|
|
680
686
|
doc,
|
|
681
687
|
idFields,
|
|
682
|
-
|
|
688
|
+
hydrateDocDataFromLocal: true
|
|
683
689
|
});
|
|
684
690
|
if (!docState.exists)
|
|
685
691
|
throw Error(ERRORS.nonExistingDoc(segments));
|
|
@@ -702,12 +708,12 @@ export async function arrayMovePublic(segments, from, to, howMany = 1) {
|
|
|
702
708
|
throw Error(ERRORS.publicDoc(segments));
|
|
703
709
|
const doc = getConnection().get(collection, docId);
|
|
704
710
|
const idFields = getIdFieldsForSegments([collection, docId]);
|
|
705
|
-
const docState = await
|
|
711
|
+
const docState = await resolvePublicDocStateWithFetchFallback({
|
|
706
712
|
collection,
|
|
707
713
|
docId,
|
|
708
714
|
doc,
|
|
709
715
|
idFields,
|
|
710
|
-
|
|
716
|
+
hydrateDocDataFromLocal: true
|
|
711
717
|
});
|
|
712
718
|
if (!docState.exists)
|
|
713
719
|
throw Error(ERRORS.nonExistingDoc(segments));
|
|
@@ -780,12 +786,12 @@ export async function stringInsertPublic(segments, index, text) {
|
|
|
780
786
|
throw Error(ERRORS.publicDoc(segments));
|
|
781
787
|
const doc = getConnection().get(collection, docId);
|
|
782
788
|
const idFields = getIdFieldsForSegments([collection, docId]);
|
|
783
|
-
const docState = await
|
|
789
|
+
const docState = await resolvePublicDocStateWithFetchFallback({
|
|
784
790
|
collection,
|
|
785
791
|
docId,
|
|
786
792
|
doc,
|
|
787
793
|
idFields,
|
|
788
|
-
|
|
794
|
+
hydrateDocDataFromLocal: true
|
|
789
795
|
});
|
|
790
796
|
if (!docState.exists)
|
|
791
797
|
throw Error(ERRORS.nonExistingDoc(segments));
|
|
@@ -814,12 +820,12 @@ export async function stringRemovePublic(segments, index, howMany) {
|
|
|
814
820
|
throw Error(ERRORS.publicDoc(segments));
|
|
815
821
|
const doc = getConnection().get(collection, docId);
|
|
816
822
|
const idFields = getIdFieldsForSegments([collection, docId]);
|
|
817
|
-
const docState = await
|
|
823
|
+
const docState = await resolvePublicDocStateWithFetchFallback({
|
|
818
824
|
collection,
|
|
819
825
|
docId,
|
|
820
826
|
doc,
|
|
821
827
|
idFields,
|
|
822
|
-
|
|
828
|
+
hydrateDocDataFromLocal: true
|
|
823
829
|
});
|
|
824
830
|
if (!docState.exists)
|
|
825
831
|
throw Error(ERRORS.nonExistingDoc(segments));
|
package/dist/orm/sub.d.ts
CHANGED
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
import type { AggregationFunction, AggregationParams, ClientAggregationFunction } from '@teamplay/utils/aggregation';
|
|
2
2
|
import type { CollectionSignal, ComputedQueryParamsInput, DocumentSignal, MaybePromise, MaybePromiseSubResult, QueryParams, RegisteredAggregationInput, SignalModelConstructor, SubResult, TypedAggregationInput, TypedAggregationSignal, WildcardSignalPath } from './Signal.js';
|
|
3
|
+
export type SubMode = 'auto' | 'fetch' | 'subscribe';
|
|
4
|
+
export interface SubOptions {
|
|
5
|
+
mode?: SubMode;
|
|
6
|
+
}
|
|
3
7
|
/**
|
|
4
8
|
* Subscribe to an aggregation with explicit output typing outside React.
|
|
5
9
|
* @param $aggregation Typed aggregation input.
|
|
6
10
|
* @param params Parameters passed to the aggregation.
|
|
7
11
|
*/
|
|
8
|
-
export default function sub<TDocument, TDocumentModel extends SignalModelConstructor<TDocument>>($aggregation: TypedAggregationInput<TDocument, TDocumentModel>, params?: AggregationParams): MaybePromise<TypedAggregationSignal<TDocument, TDocumentModel>>;
|
|
12
|
+
export default function sub<TDocument, TDocumentModel extends SignalModelConstructor<TDocument>>($aggregation: TypedAggregationInput<TDocument, TDocumentModel>, params?: AggregationParams, options?: SubOptions): MaybePromise<TypedAggregationSignal<TDocument, TDocumentModel>>;
|
|
9
13
|
/**
|
|
10
14
|
* Subscribe to a registered collection aggregation outside React.
|
|
11
15
|
* @param $aggregation Aggregation header generated by StartupJS model loading.
|
|
12
16
|
* @param params Parameters passed to the aggregation.
|
|
13
17
|
*/
|
|
14
|
-
export default function sub<TCollection extends string, TOutput = unknown>($aggregation: RegisteredAggregationInput<TCollection, TOutput>, params?: AggregationParams): MaybePromise<SubResult<RegisteredAggregationInput<TCollection, TOutput>>>;
|
|
18
|
+
export default function sub<TCollection extends string, TOutput = unknown>($aggregation: RegisteredAggregationInput<TCollection, TOutput>, params?: AggregationParams, options?: SubOptions): MaybePromise<SubResult<RegisteredAggregationInput<TCollection, TOutput>>>;
|
|
15
19
|
/**
|
|
16
20
|
* Subscribe to a client aggregation outside React.
|
|
17
21
|
* @param $aggregation Aggregation function created with `aggregation(collection, fn)`.
|
|
18
22
|
* @param params Parameters passed to the aggregation.
|
|
19
23
|
*/
|
|
20
|
-
export default function sub<TOutput, TCollection extends string>($aggregation: ClientAggregationFunction<TOutput, TCollection>, params?: AggregationParams): MaybePromise<SubResult<ClientAggregationFunction<TOutput, TCollection>>>;
|
|
24
|
+
export default function sub<TOutput, TCollection extends string>($aggregation: ClientAggregationFunction<TOutput, TCollection>, params?: AggregationParams, options?: SubOptions): MaybePromise<SubResult<ClientAggregationFunction<TOutput, TCollection>>>;
|
|
21
25
|
/**
|
|
22
26
|
* Subscribe to an unregistered aggregation outside React.
|
|
23
27
|
* @param $aggregation Aggregation function.
|
|
24
28
|
* @param params Parameters passed to the aggregation.
|
|
25
29
|
*/
|
|
26
|
-
export default function sub<TOutput = unknown, TCollection extends string = string>($aggregation: AggregationFunction<TOutput, TCollection>, params?: AggregationParams): MaybePromise<SubResult<AggregationFunction<TOutput, TCollection>>>;
|
|
30
|
+
export default function sub<TOutput = unknown, TCollection extends string = string>($aggregation: AggregationFunction<TOutput, TCollection>, params?: AggregationParams, options?: SubOptions): MaybePromise<SubResult<AggregationFunction<TOutput, TCollection>>>;
|
|
27
31
|
/**
|
|
28
32
|
* Subscribe to a document signal outside React and return it when ready.
|
|
29
33
|
* @param $signal Document signal to subscribe to.
|
|
30
34
|
*/
|
|
31
|
-
export default function sub<TSignal extends DocumentSignal<any, any, any>>($signal: TSignal): MaybePromiseSubResult<TSignal>;
|
|
35
|
+
export default function sub<TSignal extends DocumentSignal<any, any, any>>($signal: TSignal, options?: SubOptions): MaybePromiseSubResult<TSignal>;
|
|
32
36
|
/**
|
|
33
37
|
* Subscribe to a collection query outside React and return a query signal when ready.
|
|
34
38
|
* @param $collection Collection signal to query.
|
|
35
39
|
* @param params Mongo-style query params, including filters and `$sort`.
|
|
36
40
|
*/
|
|
37
|
-
export default function sub<TDocument, TCollectionModel extends SignalModelConstructor<TDocument[]>, TDocumentModel extends SignalModelConstructor<TDocument>, TCollectionPath extends WildcardSignalPath>($collection: CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, params: QueryParams<TDocument
|
|
41
|
+
export default function sub<TDocument, TCollectionModel extends SignalModelConstructor<TDocument[]>, TDocumentModel extends SignalModelConstructor<TDocument>, TCollectionPath extends WildcardSignalPath>($collection: CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, params: QueryParams<TDocument>, options?: SubOptions): MaybePromiseSubResult<CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, QueryParams<TDocument>>;
|
|
38
42
|
/**
|
|
39
43
|
* Subscribe to a collection query with computed string keys outside React.
|
|
40
44
|
* This fallback preserves Mongo-style computed paths such as `{ [`likes.${id}`]: true }`.
|
|
@@ -42,4 +46,5 @@ export default function sub<TDocument, TCollectionModel extends SignalModelConst
|
|
|
42
46
|
* @param $collection Collection signal to query.
|
|
43
47
|
* @param params Mongo-style query params with a widened computed key.
|
|
44
48
|
*/
|
|
45
|
-
export default function sub<TDocument, TCollectionModel extends SignalModelConstructor<TDocument[]>, TDocumentModel extends SignalModelConstructor<TDocument>, TCollectionPath extends WildcardSignalPath, TParams extends object>($collection: CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, params: TParams & ComputedQueryParamsInput<TParams
|
|
49
|
+
export default function sub<TDocument, TCollectionModel extends SignalModelConstructor<TDocument[]>, TDocumentModel extends SignalModelConstructor<TDocument>, TCollectionPath extends WildcardSignalPath, TParams extends object>($collection: CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, params: TParams & ComputedQueryParamsInput<TParams>, options?: SubOptions): MaybePromiseSubResult<CollectionSignal<TDocument, TCollectionModel, TDocumentModel, TCollectionPath>, TParams>;
|
|
50
|
+
export declare function unsub($signal: unknown): Promise<void> | void;
|
package/dist/orm/sub.js
CHANGED
|
@@ -3,9 +3,11 @@ import Signal, { SEGMENTS, isPublicCollectionSignal, isPublicDocumentSignal } fr
|
|
|
3
3
|
import { docSubscriptions } from './Doc.js';
|
|
4
4
|
import { querySubscriptions, getQuerySignal } from './Query.js';
|
|
5
5
|
import { aggregationSubscriptions, getAggregationSignal } from './Aggregation.js';
|
|
6
|
-
import { getRoot } from "./Root.js";
|
|
6
|
+
import { getRoot, ROOT_ID } from "./Root.js";
|
|
7
|
+
import { isRootContextClosed } from "./rootContext.js";
|
|
7
8
|
import isServer from "../utils/isServer.js";
|
|
8
|
-
|
|
9
|
+
const SUB_RECORDS = new WeakMap();
|
|
10
|
+
export default function sub($signal, params, options) {
|
|
9
11
|
// TODO: temporarily disable support for multiple subscriptions
|
|
10
12
|
// since this has to be properly cached using useDeferredSignal() in useSub()
|
|
11
13
|
// if (Array.isArray($signal)) {
|
|
@@ -16,26 +18,26 @@ export default function sub($signal, params) {
|
|
|
16
18
|
if (Array.isArray($signal))
|
|
17
19
|
throw Error('sub() does not support multiple subscriptions yet');
|
|
18
20
|
if (isRuntimePublicDocumentSignal($signal)) {
|
|
19
|
-
if (arguments.length >
|
|
21
|
+
if (arguments.length > 2) {
|
|
20
22
|
throw Error(ERRORS.subDocArguments($signal, ...Array.from(arguments).slice(1)));
|
|
21
23
|
}
|
|
22
|
-
return doc$($signal);
|
|
24
|
+
return doc$($signal, parseSubOptions(params));
|
|
23
25
|
}
|
|
24
26
|
else if (isRuntimePublicCollectionSignal($signal)) {
|
|
25
|
-
if (arguments.length
|
|
27
|
+
if (arguments.length < 2 || arguments.length > 3) {
|
|
26
28
|
throw Error(ERRORS.subQueryArguments($signal, params, ...Array.from(arguments).slice(2)));
|
|
27
29
|
}
|
|
28
|
-
return query$($signal, params);
|
|
30
|
+
return query$($signal, params, parseSubOptions(options));
|
|
29
31
|
}
|
|
30
32
|
else if (isClientAggregationFunction($signal)) {
|
|
31
|
-
return getAggregationFromFunction($signal, $signal.collection, params);
|
|
33
|
+
return getAggregationFromFunction($signal, $signal.collection, params, parseSubOptions(options));
|
|
32
34
|
}
|
|
33
35
|
else if (isAggregationHeader($signal)) {
|
|
34
36
|
const aggregationParams = {
|
|
35
37
|
$aggregationName: $signal.name,
|
|
36
38
|
$params: sanitizeAggregationParams(params)
|
|
37
39
|
};
|
|
38
|
-
return aggregation$($signal.collection, aggregationParams);
|
|
40
|
+
return aggregation$($signal.collection, aggregationParams, undefined, parseSubOptions(options));
|
|
39
41
|
}
|
|
40
42
|
else if (isAggregationFunction($signal)) {
|
|
41
43
|
if (isServer) {
|
|
@@ -46,7 +48,7 @@ export default function sub($signal, params) {
|
|
|
46
48
|
}
|
|
47
49
|
const aggregationParams = { ...serverParams };
|
|
48
50
|
delete aggregationParams.$collection;
|
|
49
|
-
return getAggregationFromFunction($signal, collection, aggregationParams);
|
|
51
|
+
return getAggregationFromFunction($signal, collection, aggregationParams, parseSubOptions(options));
|
|
50
52
|
}
|
|
51
53
|
else {
|
|
52
54
|
throw Error(ERRORS.gotAggregationFunction($signal));
|
|
@@ -59,7 +61,25 @@ export default function sub($signal, params) {
|
|
|
59
61
|
throw Error('Invalid args passed for sub()');
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
|
-
function
|
|
64
|
+
export function unsub($signal) {
|
|
65
|
+
if (!($signal instanceof Signal))
|
|
66
|
+
return;
|
|
67
|
+
const record = takeSubRecord($signal);
|
|
68
|
+
if (!record)
|
|
69
|
+
return;
|
|
70
|
+
record.disposed = true;
|
|
71
|
+
const $root = getRoot($signal);
|
|
72
|
+
const rootId = $root?.[ROOT_ID];
|
|
73
|
+
if (isRootContextClosed(rootId))
|
|
74
|
+
return;
|
|
75
|
+
if (record.kind === 'doc')
|
|
76
|
+
return docSubscriptions.unsubscribe($signal, { intent: record.intent });
|
|
77
|
+
if (record.kind === 'query')
|
|
78
|
+
return querySubscriptions.unsubscribe($signal, { intent: record.intent });
|
|
79
|
+
if (record.kind === 'aggregation')
|
|
80
|
+
return aggregationSubscriptions.unsubscribe($signal, { intent: record.intent });
|
|
81
|
+
}
|
|
82
|
+
function getAggregationFromFunction(fn, collection, params, subOptions) {
|
|
63
83
|
const aggregationParams = sanitizeAggregationParams(params);
|
|
64
84
|
let session;
|
|
65
85
|
if (isRecord(aggregationParams) && aggregationParams.$session) {
|
|
@@ -70,15 +90,13 @@ function getAggregationFromFunction(fn, collection, params) {
|
|
|
70
90
|
// should match the context in @teamplay/backend/features/serverAggregate.js
|
|
71
91
|
const context = { collection, session, isServer };
|
|
72
92
|
const result = fn(aggregationParams, context);
|
|
73
|
-
return aggregation$(collection, Array.isArray(result) ? { $aggregate: result } : result);
|
|
93
|
+
return aggregation$(collection, Array.isArray(result) ? { $aggregate: result } : result, undefined, subOptions);
|
|
74
94
|
}
|
|
75
|
-
function doc$($doc) {
|
|
76
|
-
const promise = docSubscriptions.subscribe($doc);
|
|
77
|
-
|
|
78
|
-
return $doc;
|
|
79
|
-
return new Promise(resolve => promise.then(() => resolve($doc)));
|
|
95
|
+
function doc$($doc, subOptions) {
|
|
96
|
+
const promise = docSubscriptions.subscribe($doc, { intent: subOptions.intent });
|
|
97
|
+
return returnSubscribedSignal($doc, 'doc', subOptions.intent, promise);
|
|
80
98
|
}
|
|
81
|
-
function query$($collection, params) {
|
|
99
|
+
function query$($collection, params, subOptions) {
|
|
82
100
|
const collectionName = $collection[SEGMENTS][0];
|
|
83
101
|
if (typeof collectionName !== 'string')
|
|
84
102
|
throw Error(ERRORS.queryCollectionName($collection));
|
|
@@ -86,20 +104,17 @@ function query$($collection, params) {
|
|
|
86
104
|
throw Error(ERRORS.queryParamsObject(collectionName, params));
|
|
87
105
|
const signalOptions = getQuerySignalOptions($collection);
|
|
88
106
|
const queryParams = params;
|
|
89
|
-
if (queryParams?.$aggregate || queryParams?.$aggregationName)
|
|
90
|
-
return aggregation$(collectionName, params, signalOptions);
|
|
107
|
+
if (queryParams?.$aggregate || queryParams?.$aggregationName) {
|
|
108
|
+
return aggregation$(collectionName, params, signalOptions, subOptions);
|
|
109
|
+
}
|
|
91
110
|
const $query = getQuerySignal(collectionName, params, signalOptions);
|
|
92
|
-
const promise = querySubscriptions.subscribe($query);
|
|
93
|
-
|
|
94
|
-
return $query;
|
|
95
|
-
return new Promise(resolve => promise.then(() => resolve($query)));
|
|
111
|
+
const promise = querySubscriptions.subscribe($query, { intent: subOptions.intent });
|
|
112
|
+
return returnSubscribedSignal($query, 'query', subOptions.intent, promise);
|
|
96
113
|
}
|
|
97
|
-
function aggregation$(collectionName, params, signalOptions) {
|
|
114
|
+
function aggregation$(collectionName, params, signalOptions, subOptions = parseSubOptions()) {
|
|
98
115
|
const $aggregationQuery = getAggregationSignal(collectionName, params, signalOptions);
|
|
99
|
-
const promise = aggregationSubscriptions.subscribe($aggregationQuery);
|
|
100
|
-
|
|
101
|
-
return $aggregationQuery;
|
|
102
|
-
return new Promise(resolve => promise.then(() => resolve($aggregationQuery)));
|
|
116
|
+
const promise = aggregationSubscriptions.subscribe($aggregationQuery, { intent: subOptions.intent });
|
|
117
|
+
return returnSubscribedSignal($aggregationQuery, 'aggregation', subOptions.intent, promise);
|
|
103
118
|
}
|
|
104
119
|
function api$(_fn, _args) {
|
|
105
120
|
throw Error('sub() for async functions is not implemented yet');
|
|
@@ -133,14 +148,56 @@ function isRuntimePublicCollectionSignal(value) {
|
|
|
133
148
|
function isApiFunction(value) {
|
|
134
149
|
return typeof value === 'function' && !(value instanceof Signal);
|
|
135
150
|
}
|
|
151
|
+
function parseSubOptions(options) {
|
|
152
|
+
const mode = options?.mode ?? 'auto';
|
|
153
|
+
if (mode !== 'auto' && mode !== 'fetch' && mode !== 'subscribe') {
|
|
154
|
+
throw Error(`sub() option mode must be "auto", "fetch", or "subscribe". Got: ${mode}`);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
intent: mode === 'fetch' ? 'fetch' : 'subscribe'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function returnSubscribedSignal($signal, kind, intent, promise) {
|
|
161
|
+
if (!promise) {
|
|
162
|
+
addSubRecord($signal, kind, intent);
|
|
163
|
+
return $signal;
|
|
164
|
+
}
|
|
165
|
+
return promise.then(() => {
|
|
166
|
+
addSubRecord($signal, kind, intent);
|
|
167
|
+
return $signal;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function addSubRecord($signal, kind, intent) {
|
|
171
|
+
let records = SUB_RECORDS.get($signal);
|
|
172
|
+
if (!records) {
|
|
173
|
+
records = [];
|
|
174
|
+
SUB_RECORDS.set($signal, records);
|
|
175
|
+
}
|
|
176
|
+
records.push({ kind, intent, disposed: false });
|
|
177
|
+
}
|
|
178
|
+
function takeSubRecord($signal) {
|
|
179
|
+
const records = SUB_RECORDS.get($signal);
|
|
180
|
+
if (!records)
|
|
181
|
+
return;
|
|
182
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
183
|
+
const record = records[i];
|
|
184
|
+
if (record.disposed)
|
|
185
|
+
continue;
|
|
186
|
+
records.splice(i, 1);
|
|
187
|
+
if (records.length === 0)
|
|
188
|
+
SUB_RECORDS.delete($signal);
|
|
189
|
+
return record;
|
|
190
|
+
}
|
|
191
|
+
SUB_RECORDS.delete($signal);
|
|
192
|
+
}
|
|
136
193
|
const ERRORS = {
|
|
137
194
|
subDocArguments: ($signal, ...args) => `
|
|
138
|
-
sub($doc) accepts
|
|
195
|
+
sub($doc) accepts one or two arguments - the document signal and optional options.
|
|
139
196
|
Doc: ${$signal[SEGMENTS]}
|
|
140
197
|
Got args: ${[$signal, ...args]}
|
|
141
198
|
`,
|
|
142
199
|
subQueryArguments: ($signal, params, ...args) => `
|
|
143
|
-
sub($collection, params) accepts
|
|
200
|
+
sub($collection, params) accepts two or three arguments - the collection signal, query params, and optional options.
|
|
144
201
|
If you want to subscribe to all documents in a collection, pass an empty object: sub($collection, {}).
|
|
145
202
|
Collection: ${$signal[SEGMENTS]}
|
|
146
203
|
Params: ${params}
|
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.26",
|
|
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": "a432e1c1dc272cfe2b87e3820557db4721a0f38b"
|
|
138
138
|
}
|