teamplay 0.5.0-alpha.2 → 0.5.0-alpha.21

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.
Files changed (44) hide show
  1. package/dist/index.d.ts +22 -8
  2. package/dist/index.js +1 -4
  3. package/dist/orm/Aggregation.d.ts +6 -4
  4. package/dist/orm/Aggregation.js +39 -11
  5. package/dist/orm/Compat/SignalCompat.js +141 -409
  6. package/dist/orm/Compat/queryReadiness.d.ts +13 -5
  7. package/dist/orm/Compat/silentContext.js +4 -22
  8. package/dist/orm/Compat/startStopCompat.js +33 -0
  9. package/dist/orm/Doc.js +48 -3
  10. package/dist/orm/Query.d.ts +1 -0
  11. package/dist/orm/Query.js +67 -22
  12. package/dist/orm/Signal.d.ts +3 -3
  13. package/dist/orm/SignalBase.d.ts +27 -2
  14. package/dist/orm/SignalBase.js +296 -5
  15. package/dist/orm/addModel.d.ts +9 -2
  16. package/dist/orm/getSignal.js +0 -2
  17. package/dist/orm/index.d.ts +2 -2
  18. package/dist/orm/privateData.d.ts +7 -22
  19. package/dist/orm/signalMetadata.d.ts +1 -1
  20. package/dist/orm/signalMetadata.js +29 -3
  21. package/dist/orm/signalReads.d.ts +1 -1
  22. package/dist/orm/signalReads.js +7 -7
  23. package/dist/orm/signalSymbols.js +1 -1
  24. package/dist/orm/sub.d.ts +9 -1
  25. package/dist/orm/subscriptionGcDelay.js +2 -6
  26. package/dist/orm/types/baseMethods.d.ts +2 -1
  27. package/dist/orm/types/jsonSchema.d.ts +3 -3
  28. package/dist/orm/types/modelManifest.d.ts +12 -1
  29. package/dist/orm/types/query.d.ts +7 -1
  30. package/dist/orm/types/signal.d.ts +44 -15
  31. package/dist/react/convertToObserver.js +1 -4
  32. package/dist/react/promiseBatcher.js +1 -1
  33. package/dist/react/renderAttemptDestroyer.d.ts +0 -8
  34. package/dist/react/renderAttemptDestroyer.js +2 -28
  35. package/dist/react/trapRender.js +3 -3
  36. package/dist/react/useSub.d.ts +104 -5
  37. package/dist/react/useSub.js +191 -32
  38. package/dist/react/useSuspendMemo.js +1 -5
  39. package/dist/server.d.ts +1 -1
  40. package/package.json +16 -10
  41. package/dist/orm/Compat/hooksCompat.d.ts +0 -33
  42. package/dist/orm/Compat/hooksCompat.js +0 -360
  43. package/dist/react/compatComponentRegistry.d.ts +0 -4
  44. package/dist/react/compatComponentRegistry.js +0 -19
@@ -1,11 +1,17 @@
1
1
  import { useRef, useDeferredValue } from 'react';
2
2
  import sub from "../orm/sub.js";
3
- import { useScheduleUpdate, useCache, useDefer, useId } from "./helpers.js";
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 renderAttemptDestroyer from "./renderAttemptDestroyer.js";
7
- import { markCompatComponent } from "./compatComponentRegistry.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";
12
+ import { isPublicDocumentSignal } from "../orm/Signal.js";
8
13
  const runtimeSub = sub;
14
+ const USE_SUB_OPTION_KEYS = new Set(['async', 'defer', 'batch']);
9
15
  let TEST_THROTTLING = false;
10
16
  // experimental feature to leverage useDeferredValue() to handle re-subscriptions.
11
17
  // Currently it does lead to issues with extra rerenders and requires further investigation
@@ -13,12 +19,39 @@ let USE_DEFERRED_VALUE = true;
13
19
  // by default we want to defer stuff if possible instead of throwing promises
14
20
  let DEFAULT_DEFER = true;
15
21
  export function useAsyncSub(signal, params, options) {
16
- return runUseSub(signal, params, { ...options, async: true });
22
+ const normalized = normalizeUseSubArgs(signal, params, options);
23
+ return runUseSub(normalized.signal, normalized.params, { ...normalized.options, async: true });
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 });
17
32
  }
18
33
  export default function useSub(signal, params, options) {
19
- return runUseSub(signal, params, options);
34
+ const normalized = normalizeUseSubArgs(signal, params, options);
35
+ return runUseSub(normalized.signal, normalized.params, normalized.options);
36
+ }
37
+ function normalizeUseSubArgs(signal, params, options) {
38
+ if (options === undefined && params !== undefined && isPublicDocumentSignal(signal) && isUseSubOptions(params)) {
39
+ return {
40
+ signal,
41
+ params: undefined,
42
+ options: params
43
+ };
44
+ }
45
+ return { signal, params, options };
46
+ }
47
+ function isUseSubOptions(value) {
48
+ if (value == null || typeof value !== 'object' || Array.isArray(value))
49
+ return false;
50
+ return Object.keys(value).every(key => USE_SUB_OPTION_KEYS.has(key));
20
51
  }
21
52
  function runUseSub(signal, params, options) {
53
+ if (isBatchBarrierCall(signal, params, options))
54
+ return closeBatchBarrier();
22
55
  if (USE_DEFERRED_VALUE) {
23
56
  return useSubDeferred(signal, params, options); // eslint-disable-line react-hooks/rules-of-hooks
24
57
  }
@@ -26,14 +59,19 @@ function runUseSub(signal, params, options) {
26
59
  return useSubClassic(signal, params, options); // eslint-disable-line react-hooks/rules-of-hooks
27
60
  }
28
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
+ }
29
70
  // version of sub() which works as a react hook and throws promise for Suspense
30
- export function useSubDeferred(signal, params, { async = false, defer, batch = false, compatAttemptCleanup = false } = {}) {
71
+ export function useSubDeferred(signal, params, { async = false, defer, batch = false } = {}) {
31
72
  const $signalRef = useRef();
32
- const componentId = useId();
33
73
  const scheduleUpdate = useScheduleUpdate();
34
74
  const observerDefer = useDefer();
35
- if (compatAttemptCleanup)
36
- markCompatComponent(componentId);
37
75
  if (batch)
38
76
  promiseBatcher.activate();
39
77
  defer ??= observerDefer ?? DEFAULT_DEFER;
@@ -46,20 +84,20 @@ export function useSubDeferred(signal, params, { async = false, defer, batch = f
46
84
  // 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
47
85
  if (isThenable(promiseOrSignal)) {
48
86
  const promise = maybeThrottle(promiseOrSignal);
87
+ const readyPromise = batch ? getBatchReadyPromise(promise) : promise;
49
88
  const hasPreviousSignal = !!$signalRef.current;
50
89
  if (batch) {
51
90
  // Batch suspense must block only on initial load.
52
91
  // On resubscribe we keep rendering previous signal and refresh in background.
53
92
  if (!hasPreviousSignal) {
54
93
  promiseBatcher.add(promise);
55
- if (compatAttemptCleanup)
56
- registerCompatAttemptCleanup(signal, params);
94
+ addBatchReadinessCheck(promise);
57
95
  }
58
96
  else {
59
- scheduleUpdate(promise);
97
+ scheduleUpdate(readyPromise);
60
98
  }
61
99
  if (async)
62
- scheduleUpdate(promise);
100
+ scheduleUpdate(readyPromise);
63
101
  return $signalRef.current;
64
102
  }
65
103
  if (async) {
@@ -71,13 +109,13 @@ export function useSubDeferred(signal, params, { async = false, defer, batch = f
71
109
  scheduleUpdate(promise);
72
110
  return $signalRef.current;
73
111
  }
74
- if (compatAttemptCleanup)
75
- registerCompatAttemptCleanup(signal, params);
76
112
  throw promise;
77
113
  // 2. if it's a signal, we save it into ref to make sure it's not garbage collected while component exists
78
114
  }
79
115
  else {
80
116
  const $signal = promiseOrSignal;
117
+ if (batch && !$signalRef.current)
118
+ addBatchReadinessCheckForSignal($signal);
81
119
  if ($signalRef.current !== $signal)
82
120
  $signalRef.current = $signal;
83
121
  return $signal;
@@ -85,33 +123,30 @@ export function useSubDeferred(signal, params, { async = false, defer, batch = f
85
123
  }
86
124
  // classic version which initially throws promise for Suspense
87
125
  // but if we get a promise second time, we return the last signal and wait for promise to resolve
88
- export function useSubClassic(signal, params, { async = false, batch = false, compatAttemptCleanup = false } = {}) {
126
+ export function useSubClassic(signal, params, { async = false, batch = false } = {}) {
89
127
  const id = executionContextTracker.newHookId();
90
- const componentId = useId();
91
128
  const cache = useCache(undefined);
92
129
  const scheduleUpdate = useScheduleUpdate();
93
- if (compatAttemptCleanup)
94
- markCompatComponent(componentId);
95
130
  if (batch)
96
131
  promiseBatcher.activate();
97
132
  const promiseOrSignal = params != null ? runtimeSub(signal, params) : runtimeSub(signal);
98
133
  // 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
99
134
  if (isThenable(promiseOrSignal)) {
100
135
  const promise = maybeThrottle(promiseOrSignal);
136
+ const readyPromise = batch ? getBatchReadyPromise(promise) : promise;
101
137
  if (batch) {
102
138
  const hasPreviousSignal = cache.has(id);
103
139
  // Batch suspense must block only on initial load.
104
140
  // On resubscribe we keep rendering previous signal and refresh in background.
105
141
  if (!hasPreviousSignal) {
106
142
  promiseBatcher.add(promise);
107
- if (compatAttemptCleanup)
108
- registerCompatAttemptCleanup(signal, params);
143
+ addBatchReadinessCheck(promise);
109
144
  }
110
145
  else {
111
- scheduleUpdate(promise);
146
+ scheduleUpdate(readyPromise);
112
147
  }
113
148
  if (async)
114
- scheduleUpdate(promise);
149
+ scheduleUpdate(readyPromise);
115
150
  if (hasPreviousSignal)
116
151
  return cache.get(id);
117
152
  return;
@@ -126,8 +161,6 @@ export function useSubClassic(signal, params, { async = false, batch = false, co
126
161
  scheduleUpdate(promise);
127
162
  return;
128
163
  }
129
- if (compatAttemptCleanup)
130
- registerCompatAttemptCleanup(signal, params);
131
164
  // in regular mode we throw the promise to be caught by Suspense
132
165
  // this way we guarantee that the signal with all the data
133
166
  // will always be there when component is rendered
@@ -140,6 +173,8 @@ export function useSubClassic(signal, params, { async = false, batch = false, co
140
173
  }
141
174
  else {
142
175
  const $signal = promiseOrSignal;
176
+ if (batch && !cache.has(id))
177
+ addBatchReadinessCheckForSignal($signal);
143
178
  if (cache.get(id) !== $signal) {
144
179
  cache.set(id, $signal);
145
180
  }
@@ -175,15 +210,139 @@ function maybeThrottle(promise) {
175
210
  }, delay);
176
211
  });
177
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
+ }
178
344
  function isThenable(value) {
179
345
  return !!value &&
180
346
  (typeof value === 'object' || typeof value === 'function') &&
181
347
  typeof value.then === 'function';
182
348
  }
183
- function registerCompatAttemptCleanup(_signal, _params) {
184
- // Compat hooks don't build per-hook init objects like Racer.
185
- // We still need a marker so trapRender can defer observer-shell cleanup
186
- // only when a real attempt cleanup exists.
187
- // This path must not arm suspense-gate keep-alive by itself.
188
- renderAttemptDestroyer.armCompatAttemptCleanup();
189
- }
@@ -1,13 +1,11 @@
1
1
  import executionContextTracker from "./executionContextTracker.js";
2
- import { useCache, useId } from "./helpers.js";
3
- import { markCompatComponent } from "./compatComponentRegistry.js";
2
+ import { useCache } from "./helpers.js";
4
3
  import renderAttemptDestroyer from "./renderAttemptDestroyer.js";
5
4
  const IN_FLIGHT_BY_KEY = new Map();
6
5
  export default function useSuspendMemo(factory, deps) {
7
6
  if (typeof factory !== 'function')
8
7
  throw Error('useSuspendMemo() expects a factory function');
9
8
  deps = normalizeDeps(deps);
10
- const componentId = useId();
11
9
  const cache = useCache(undefined);
12
10
  const hookId = executionContextTracker.newHookId();
13
11
  const cacheKey = `suspendMemo:${hookId}`;
@@ -24,7 +22,6 @@ export default function useSuspendMemo(factory, deps) {
24
22
  if (entry.status === 'done')
25
23
  return entry.value;
26
24
  if (entry.status === 'pending') {
27
- markCompatComponent(componentId);
28
25
  renderAttemptDestroyer.armSuspenseGate();
29
26
  throw entry.promise;
30
27
  }
@@ -45,7 +42,6 @@ export default function useSuspendMemo(factory, deps) {
45
42
  });
46
43
  entry.status = 'pending';
47
44
  entry.promise = promise;
48
- markCompatComponent(componentId);
49
45
  renderAttemptDestroyer.armSuspenseGate();
50
46
  throw promise;
51
47
  }
package/dist/server.d.ts CHANGED
@@ -8,4 +8,4 @@ export function initConnection(backend: any, { fetchOnly, publicOnly, ...options
8
8
  wss: any;
9
9
  };
10
10
  export default createBackend;
11
- export { mongo, mongoClient, createMongoIndex, redis, redlock, sqlite } from "@teamplay/backend";
11
+ export { mongo, mongoClient, createMongoIndex, redis, redlock, sqlite, getRedis } from "@teamplay/backend";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamplay",
3
- "version": "0.5.0-alpha.2",
3
+ "version": "0.5.0-alpha.21",
4
4
  "description": "Full-stack signals ORM with multiplayer",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -69,20 +69,20 @@
69
69
  "dependencies": {
70
70
  "@nx-js/observer-util": "^4.1.3",
71
71
  "@startupjs/sharedb-mingo-memory": "^4.0.0-2",
72
- "@teamplay/backend": "^0.5.0-alpha.1",
73
- "@teamplay/cache": "^0.5.0-alpha.0",
74
- "@teamplay/channel": "^0.5.0-alpha.0",
75
- "@teamplay/debug": "^0.5.0-alpha.0",
76
- "@teamplay/schema": "^0.5.0-alpha.1",
77
- "@teamplay/utils": "^0.5.0-alpha.0",
78
- "babel-plugin-teamplay": "^0.5.0-alpha.1",
72
+ "@teamplay/backend": "^0.5.0-alpha.13",
73
+ "@teamplay/cache": "^0.5.0-alpha.7",
74
+ "@teamplay/channel": "^0.5.0-alpha.10",
75
+ "@teamplay/debug": "^0.5.0-alpha.7",
76
+ "@teamplay/schema": "^0.5.0-alpha.13",
77
+ "@teamplay/utils": "^0.5.0-alpha.7",
78
+ "babel-plugin-teamplay": "^0.5.0-alpha.13",
79
79
  "diff-match-patch": "^1.0.5",
80
80
  "events": "^3.3.0",
81
81
  "json0-ot-diff": "^1.1.2",
82
82
  "localforage": "^1.10.0",
83
83
  "lodash": "^4.17.20",
84
84
  "pluralize": "^8.0.0",
85
- "sharedb": "^5.0.0",
85
+ "sharedb": "5.2.2",
86
86
  "stream": "npm:readable-stream@^4.7.0"
87
87
  },
88
88
  "devDependencies": {
@@ -116,6 +116,12 @@
116
116
  "transform": {
117
117
  "^.+\\.ts$": "./test/ts-transform.cjs"
118
118
  },
119
+ "testEnvironmentOptions": {
120
+ "customExportConditions": [
121
+ "teamplay-ts",
122
+ "browser"
123
+ ]
124
+ },
119
125
  "extensionsToTreatAsEsm": [
120
126
  ".ts"
121
127
  ],
@@ -128,5 +134,5 @@
128
134
  ]
129
135
  },
130
136
  "license": "MIT",
131
- "gitHead": "6d03dc309c39801dac31320a089dc30327613123"
137
+ "gitHead": "9a68b281123d93db5af433fc3e76c6e086b09ac5"
132
138
  }
@@ -1,33 +0,0 @@
1
- export const useValue: any
2
- export const useValue$: any
3
- export const useModel: any
4
- export const useLocal: any
5
- export const useLocal$: any
6
- export const useLocalDoc: any
7
- export const useLocalDoc$: any
8
- export const useSession: any
9
- export const useSession$: any
10
- export const usePage: any
11
- export const usePage$: any
12
- export const useBatch: any
13
- export const useDoc: any
14
- export const useDoc$: any
15
- export const useBatchDoc: any
16
- export const useBatchDoc$: any
17
- export const useAsyncDoc: any
18
- export const useAsyncDoc$: any
19
- export const useQuery: any
20
- export const useQuery$: any
21
- export const useAsyncQuery: any
22
- export const useAsyncQuery$: any
23
- export const useBatchQuery: any
24
- export const useBatchQuery$: any
25
- export const useQueryIds: any
26
- export const useBatchQueryIds: any
27
- export const useAsyncQueryIds: any
28
- export const useQueryDoc: any
29
- export const useQueryDoc$: any
30
- export const useBatchQueryDoc: any
31
- export const useBatchQueryDoc$: any
32
- export const useAsyncQueryDoc: any
33
- export const useAsyncQueryDoc$: any