jazz-tools 0.19.19 → 0.19.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.
- package/.svelte-kit/__package__/client.d.ts.map +1 -1
- package/.svelte-kit/__package__/client.js +3 -1
- package/.svelte-kit/__package__/server.d.ts.map +1 -1
- package/.svelte-kit/__package__/server.js +9 -7
- package/.svelte-kit/__package__/tests/client.test.js +48 -0
- package/.turbo/turbo-build.log +70 -66
- package/dist/better-auth/auth/client.d.ts.map +1 -1
- package/dist/better-auth/auth/client.js +1 -1
- package/dist/better-auth/auth/client.js.map +1 -1
- package/dist/better-auth/auth/server.d.ts.map +1 -1
- package/dist/better-auth/auth/server.js +4 -4
- package/dist/better-auth/auth/server.js.map +1 -1
- package/dist/better-auth/database-adapter/index.js.map +1 -1
- package/dist/better-auth/database-adapter/repository/generic.d.ts +3 -3
- package/dist/better-auth/database-adapter/repository/session.d.ts +2 -2
- package/dist/better-auth/database-adapter/schema.d.ts +3 -3
- package/dist/better-auth/database-adapter/schema.d.ts.map +1 -1
- package/dist/{chunk-PEHQ7TN2.js → chunk-QCTQH5RS.js} +31 -4
- package/dist/chunk-QCTQH5RS.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react/hooks.d.ts +1 -2
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/index.js +7 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react-core/hooks.d.ts +94 -3
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +130 -135
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-core/tests/useCoStates.test.d.ts +2 -0
- package/dist/react-core/tests/useCoStates.test.d.ts.map +1 -0
- package/dist/react-native/chunk-DGUM43GV.js +11 -0
- package/dist/react-native/chunk-DGUM43GV.js.map +1 -0
- package/dist/react-native/crypto.js +2 -0
- package/dist/react-native/crypto.js.map +1 -1
- package/dist/react-native/index.js +544 -29
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native-core/auth/PasskeyAuth.d.ts +123 -0
- package/dist/react-native-core/auth/PasskeyAuth.d.ts.map +1 -0
- package/dist/react-native-core/auth/PasskeyAuthBasicUI.d.ts +34 -0
- package/dist/react-native-core/auth/PasskeyAuthBasicUI.d.ts.map +1 -0
- package/dist/react-native-core/auth/auth.d.ts +3 -0
- package/dist/react-native-core/auth/auth.d.ts.map +1 -1
- package/dist/react-native-core/auth/passkey-utils.d.ts +16 -0
- package/dist/react-native-core/auth/passkey-utils.d.ts.map +1 -0
- package/dist/react-native-core/auth/usePasskeyAuth.d.ts +48 -0
- package/dist/react-native-core/auth/usePasskeyAuth.d.ts.map +1 -0
- package/dist/react-native-core/chunk-DGUM43GV.js +11 -0
- package/dist/react-native-core/chunk-DGUM43GV.js.map +1 -0
- package/dist/react-native-core/crypto.js +2 -0
- package/dist/react-native-core/crypto.js.map +1 -1
- package/dist/react-native-core/hooks.d.ts +1 -1
- package/dist/react-native-core/hooks.d.ts.map +1 -1
- package/dist/react-native-core/index.js +539 -24
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/tests/PasskeyAuth.test.d.ts +2 -0
- package/dist/react-native-core/tests/PasskeyAuth.test.d.ts.map +1 -0
- package/dist/react-native-core/tests/passkey-utils.test.d.ts +2 -0
- package/dist/react-native-core/tests/passkey-utils.test.d.ts.map +1 -0
- package/dist/svelte/auth/ClerkAuth.svelte.d.ts +38 -0
- package/dist/svelte/auth/ClerkAuth.svelte.d.ts.map +1 -0
- package/dist/svelte/auth/ClerkAuth.svelte.js +47 -0
- package/dist/svelte/auth/JazzSvelteProviderWithClerk.svelte +156 -0
- package/dist/svelte/auth/JazzSvelteProviderWithClerk.svelte.d.ts +67 -0
- package/dist/svelte/auth/JazzSvelteProviderWithClerk.svelte.d.ts.map +1 -0
- package/dist/svelte/auth/RegisterClerkAuth.svelte +27 -0
- package/dist/svelte/auth/RegisterClerkAuth.svelte.d.ts +17 -0
- package/dist/svelte/auth/RegisterClerkAuth.svelte.d.ts.map +1 -0
- package/dist/svelte/auth/index.d.ts +2 -0
- package/dist/svelte/auth/index.d.ts.map +1 -1
- package/dist/svelte/auth/index.js +2 -0
- package/dist/svelte/tests/ClerkAuth.svelte.test.d.ts +2 -0
- package/dist/svelte/tests/ClerkAuth.svelte.test.d.ts.map +1 -0
- package/dist/svelte/tests/ClerkAuth.svelte.test.js +202 -0
- package/dist/svelte/tests/TestClerkAuthWrapper.svelte +16 -0
- package/dist/svelte/tests/TestClerkAuthWrapper.svelte.d.ts +8 -0
- package/dist/svelte/tests/TestClerkAuthWrapper.svelte.d.ts.map +1 -0
- package/dist/svelte/tests/testUtils.d.ts +1 -0
- package/dist/svelte/tests/testUtils.d.ts.map +1 -1
- package/dist/svelte/tests/testUtils.js +3 -1
- package/dist/testing.js +1 -1
- package/dist/tools/auth/clerk/index.d.ts +1 -1
- package/dist/tools/auth/clerk/types.d.ts +1 -1
- package/dist/tools/auth/clerk/types.d.ts.map +1 -1
- package/dist/tools/coValues/account.d.ts +5 -1
- package/dist/tools/coValues/account.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +30 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/subscribe/types.d.ts +1 -1
- package/dist/tools/subscribe/types.d.ts.map +1 -1
- package/dist/tools/testing.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/better-auth/auth/client.ts +3 -1
- package/src/better-auth/auth/server.ts +9 -7
- package/src/better-auth/auth/tests/client.test.ts +66 -2
- package/src/better-auth/database-adapter/repository/generic.ts +3 -3
- package/src/better-auth/database-adapter/repository/session.ts +2 -2
- package/src/better-auth/database-adapter/schema.ts +5 -5
- package/src/react/hooks.tsx +4 -2
- package/src/react-core/hooks.ts +332 -178
- package/src/react-core/tests/useCoState.selector.test.ts +309 -22
- package/src/react-core/tests/useCoStates.test.tsx +414 -0
- package/src/react-native-core/auth/PasskeyAuth.ts +316 -0
- package/src/react-native-core/auth/PasskeyAuthBasicUI.tsx +284 -0
- package/src/react-native-core/auth/auth.ts +3 -0
- package/src/react-native-core/auth/passkey-utils.ts +47 -0
- package/src/react-native-core/auth/usePasskeyAuth.tsx +85 -0
- package/src/react-native-core/hooks.tsx +2 -0
- package/src/react-native-core/tests/PasskeyAuth.test.ts +463 -0
- package/src/react-native-core/tests/passkey-utils.test.ts +144 -0
- package/src/svelte/auth/ClerkAuth.svelte.ts +67 -0
- package/src/svelte/auth/JazzSvelteProviderWithClerk.svelte +156 -0
- package/src/svelte/auth/RegisterClerkAuth.svelte +27 -0
- package/src/svelte/auth/index.ts +2 -0
- package/src/svelte/tests/ClerkAuth.svelte.test.ts +305 -0
- package/src/svelte/tests/TestClerkAuthWrapper.svelte +16 -0
- package/src/svelte/tests/testUtils.ts +4 -1
- package/src/tools/auth/clerk/types.ts +1 -1
- package/src/tools/coValues/account.ts +11 -3
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +27 -1
- package/src/tools/subscribe/types.ts +1 -1
- package/src/tools/tests/account.test.ts +2 -1
- package/src/tools/tests/inbox.test.ts +7 -7
- package/testSetup.ts +4 -0
- package/vitest.config.ts +1 -0
- package/dist/chunk-PEHQ7TN2.js.map +0 -1
package/src/react-core/hooks.ts
CHANGED
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
Loaded,
|
|
24
24
|
MaybeLoaded,
|
|
25
25
|
NotLoaded,
|
|
26
|
-
RefsToResolve,
|
|
27
26
|
ResolveQuery,
|
|
28
27
|
ResolveQueryStrict,
|
|
29
28
|
SchemaResolveQuery,
|
|
@@ -103,94 +102,107 @@ export function useCoValueSubscription<
|
|
|
103
102
|
resolve?: ResolveQueryStrict<S, R>;
|
|
104
103
|
unstable_branch?: BranchDefinition;
|
|
105
104
|
},
|
|
106
|
-
) {
|
|
105
|
+
): CoValueSubscription<S, R> | null {
|
|
106
|
+
const resolve = getResolveQuery(Schema, options?.resolve);
|
|
107
|
+
const subscriptions = useCoValueSubscriptions(
|
|
108
|
+
Schema,
|
|
109
|
+
[id],
|
|
110
|
+
resolve,
|
|
111
|
+
options?.unstable_branch,
|
|
112
|
+
);
|
|
113
|
+
return (subscriptions[0] ?? null) as CoValueSubscription<S, R> | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tracked state for the entire subscriptions array.
|
|
118
|
+
* If any of the dependencies change, the subscriptions are recreated.
|
|
119
|
+
*/
|
|
120
|
+
interface SubscriptionsState {
|
|
121
|
+
subscriptions: (SubscriptionScope<CoValue> | null)[];
|
|
122
|
+
schema: CoValueClassOrSchema;
|
|
123
|
+
ids: readonly (string | undefined | null)[];
|
|
124
|
+
resolve: ResolveQuery<any>;
|
|
125
|
+
contextManager: ReturnType<typeof useJazzContextManager>;
|
|
126
|
+
agent: AnonymousJazzAgent | Loaded<any, true>;
|
|
127
|
+
branchName?: string;
|
|
128
|
+
branchOwnerId?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Internal hook that manages an array of SubscriptionScope instances.
|
|
133
|
+
*
|
|
134
|
+
* - Uses a ref to track subscriptions by index
|
|
135
|
+
* - Detects changes by comparing schema/ids/resolve/branch
|
|
136
|
+
* - Creates new subscriptions via SubscriptionScopeCache.getOrCreate()
|
|
137
|
+
* - Returns null for entries with undefined/null IDs or invalid branches
|
|
138
|
+
*/
|
|
139
|
+
function useCoValueSubscriptions(
|
|
140
|
+
schema: CoValueClassOrSchema,
|
|
141
|
+
ids: readonly (string | undefined | null)[],
|
|
142
|
+
resolve: ResolveQuery<any>,
|
|
143
|
+
branch?: BranchDefinition,
|
|
144
|
+
): (SubscriptionScope<CoValue> | null)[] {
|
|
107
145
|
const contextManager = useJazzContextManager();
|
|
108
146
|
const agent = useAgent();
|
|
109
147
|
|
|
110
|
-
const callerStack =
|
|
148
|
+
const callerStack = useMemo(() => captureStack(), []);
|
|
111
149
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
150
|
+
const createAllSubscriptions = (): SubscriptionsState => {
|
|
151
|
+
const node = contextManager.getCurrentValue()!.node;
|
|
152
|
+
const cache = contextManager.getSubscriptionScopeCache();
|
|
115
153
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
contextManager,
|
|
121
|
-
id,
|
|
122
|
-
Schema,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
154
|
+
const subscriptions = ids.map((id) => {
|
|
155
|
+
if (id === undefined || id === null) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
125
158
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
contextManager,
|
|
159
|
+
const subscription = cache.getOrCreate(
|
|
160
|
+
node,
|
|
161
|
+
schema,
|
|
130
162
|
id,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
node,
|
|
141
|
-
Schema,
|
|
142
|
-
id,
|
|
143
|
-
resolve,
|
|
144
|
-
false,
|
|
145
|
-
false,
|
|
146
|
-
options?.unstable_branch,
|
|
147
|
-
);
|
|
163
|
+
resolve,
|
|
164
|
+
false,
|
|
165
|
+
false,
|
|
166
|
+
branch,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (callerStack) {
|
|
170
|
+
subscription.callerStack = callerStack;
|
|
171
|
+
}
|
|
148
172
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
subscription.callerStack = callerStack.current;
|
|
152
|
-
}
|
|
173
|
+
return subscription;
|
|
174
|
+
});
|
|
153
175
|
|
|
154
176
|
return {
|
|
155
|
-
|
|
177
|
+
subscriptions,
|
|
178
|
+
schema,
|
|
179
|
+
ids,
|
|
180
|
+
resolve,
|
|
156
181
|
contextManager,
|
|
157
|
-
id,
|
|
158
|
-
Schema,
|
|
159
|
-
branchName: options?.unstable_branch?.name,
|
|
160
|
-
branchOwnerId: options?.unstable_branch?.owner?.$jazz.id,
|
|
161
182
|
agent,
|
|
183
|
+
branchName: branch?.name,
|
|
184
|
+
branchOwnerId: branch?.owner?.$jazz.id,
|
|
162
185
|
};
|
|
163
186
|
};
|
|
164
187
|
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
>>(null);
|
|
188
|
+
const stateRef = React.useRef<SubscriptionsState | null>(null);
|
|
189
|
+
const newSubscriptions = createAllSubscriptions();
|
|
168
190
|
|
|
169
|
-
|
|
170
|
-
subscriptionRef.current = createSubscription();
|
|
171
|
-
}
|
|
191
|
+
const state = stateRef.current;
|
|
172
192
|
|
|
173
|
-
|
|
174
|
-
const
|
|
193
|
+
// Avoid recreating the subscriptions array if all subscriptions are already cached
|
|
194
|
+
const anySubscriptionChanged =
|
|
195
|
+
newSubscriptions.subscriptions.length !== state?.subscriptions.length ||
|
|
196
|
+
newSubscriptions.subscriptions.some(
|
|
197
|
+
(newSubscriptions, index) =>
|
|
198
|
+
newSubscriptions !== state.subscriptions[index],
|
|
199
|
+
);
|
|
175
200
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// Check if the subscription needs to be updated
|
|
179
|
-
// because one of the dependencies has changed
|
|
180
|
-
if (
|
|
181
|
-
subscription.contextManager !== contextManager ||
|
|
182
|
-
subscription.id !== id ||
|
|
183
|
-
subscription.Schema !== Schema ||
|
|
184
|
-
subscription.branchName !== branchName ||
|
|
185
|
-
subscription.branchOwnerId !== branchOwnerId ||
|
|
186
|
-
subscription.agent !== agent
|
|
187
|
-
) {
|
|
188
|
-
subscriptionRef.current = createSubscription();
|
|
189
|
-
subscription = subscriptionRef.current;
|
|
201
|
+
if (anySubscriptionChanged) {
|
|
202
|
+
stateRef.current = newSubscriptions;
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
|
|
193
|
-
return subscription.value as CoValueSubscription<S, R>;
|
|
205
|
+
return stateRef.current!.subscriptions;
|
|
194
206
|
}
|
|
195
207
|
|
|
196
208
|
function useImportCoValueContent<V>(
|
|
@@ -417,31 +429,8 @@ export function useCoState<
|
|
|
417
429
|
},
|
|
418
430
|
): TSelectorReturn {
|
|
419
431
|
useImportCoValueContent(id, options?.preloaded);
|
|
420
|
-
|
|
421
432
|
const subscription = useCoValueSubscription(Schema, id, options);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const value = useSyncExternalStoreWithSelector<
|
|
425
|
-
MaybeLoaded<Loaded<S, R>>,
|
|
426
|
-
TSelectorReturn
|
|
427
|
-
>(
|
|
428
|
-
React.useCallback(
|
|
429
|
-
(callback) => {
|
|
430
|
-
if (!subscription) {
|
|
431
|
-
return () => {};
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return subscription.subscribe(callback);
|
|
435
|
-
},
|
|
436
|
-
[subscription],
|
|
437
|
-
),
|
|
438
|
-
getCurrentValue,
|
|
439
|
-
getCurrentValue,
|
|
440
|
-
options?.select ?? ((value) => value as TSelectorReturn),
|
|
441
|
-
options?.equalityFn ?? Object.is,
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
return value;
|
|
433
|
+
return useSubscriptionSelector(subscription, options);
|
|
445
434
|
}
|
|
446
435
|
|
|
447
436
|
export function useSuspenseCoState<
|
|
@@ -491,50 +480,34 @@ export function useSuspenseCoState<
|
|
|
491
480
|
|
|
492
481
|
use(subscription.getCachedPromise());
|
|
493
482
|
|
|
494
|
-
|
|
495
|
-
const value = subscription.getCurrentValue();
|
|
496
|
-
|
|
497
|
-
if (!value.$isLoaded) {
|
|
498
|
-
throw new Error("CoValue must be loaded in a suspense context");
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
return value;
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
const value = useSyncExternalStoreWithSelector<Loaded<S, R>, TSelectorReturn>(
|
|
505
|
-
React.useCallback(
|
|
506
|
-
(callback) => {
|
|
507
|
-
return subscription.subscribe(callback);
|
|
508
|
-
},
|
|
509
|
-
[subscription],
|
|
510
|
-
),
|
|
511
|
-
getCurrentValue,
|
|
512
|
-
getCurrentValue,
|
|
513
|
-
options?.select ?? ((value) => value as TSelectorReturn),
|
|
514
|
-
options?.equalityFn ?? Object.is,
|
|
515
|
-
);
|
|
516
|
-
|
|
517
|
-
return value;
|
|
483
|
+
return useSubscriptionSelector(subscription, options);
|
|
518
484
|
}
|
|
519
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Returns a subscription's current value.
|
|
488
|
+
* Allows to optionally select a subset of the subscription's value.
|
|
489
|
+
*
|
|
490
|
+
* This is the single-value counterpart to {@link useSubscriptionsSelector}.
|
|
491
|
+
* Keeping it separate for performance reasons.
|
|
492
|
+
*/
|
|
520
493
|
export function useSubscriptionSelector<
|
|
521
494
|
S extends CoValueClassOrSchema,
|
|
522
495
|
// @ts-expect-error we can't statically enforce the schema's resolve query is a valid resolve query, but in practice it is
|
|
523
496
|
const R extends ResolveQuery<S> = SchemaResolveQuery<S>,
|
|
524
|
-
|
|
497
|
+
// Selector input can be an already loaded or a maybe-loaded value,
|
|
498
|
+
// depending on whether a suspense hook is used or not, respectively.
|
|
499
|
+
TSelectorInput = MaybeLoaded<Loaded<S, R>>,
|
|
500
|
+
TSelectorReturn = TSelectorInput,
|
|
525
501
|
>(
|
|
526
502
|
subscription: CoValueSubscription<S, R>,
|
|
527
503
|
options?: {
|
|
528
|
-
select?: (value:
|
|
504
|
+
select?: (value: TSelectorInput) => TSelectorReturn;
|
|
529
505
|
equalityFn?: (a: TSelectorReturn, b: TSelectorReturn) => boolean;
|
|
530
506
|
},
|
|
531
|
-
) {
|
|
507
|
+
): TSelectorReturn {
|
|
532
508
|
const getCurrentValue = useGetCurrentValue(subscription);
|
|
533
509
|
|
|
534
|
-
return useSyncExternalStoreWithSelector
|
|
535
|
-
MaybeLoaded<Loaded<S, R>>,
|
|
536
|
-
TSelectorReturn
|
|
537
|
-
>(
|
|
510
|
+
return useSyncExternalStoreWithSelector(
|
|
538
511
|
React.useCallback(
|
|
539
512
|
(callback) => {
|
|
540
513
|
if (!subscription) {
|
|
@@ -547,7 +520,7 @@ export function useSubscriptionSelector<
|
|
|
547
520
|
),
|
|
548
521
|
getCurrentValue,
|
|
549
522
|
getCurrentValue,
|
|
550
|
-
options?.select ?? ((value) => value as TSelectorReturn),
|
|
523
|
+
options?.select ?? ((value) => value as unknown as TSelectorReturn),
|
|
551
524
|
options?.equalityFn ?? Object.is,
|
|
552
525
|
);
|
|
553
526
|
}
|
|
@@ -566,10 +539,7 @@ export function useAccountSubscription<
|
|
|
566
539
|
const contextManager = useJazzContextManager();
|
|
567
540
|
|
|
568
541
|
// Capture stack trace at hook call time
|
|
569
|
-
const callerStack =
|
|
570
|
-
if (!callerStack.current) {
|
|
571
|
-
callerStack.current = captureStack();
|
|
572
|
-
}
|
|
542
|
+
const callerStack = useMemo(() => captureStack(), []);
|
|
573
543
|
|
|
574
544
|
const createSubscription = () => {
|
|
575
545
|
const agent = getCurrentAccountFromContextManager(contextManager);
|
|
@@ -597,8 +567,8 @@ export function useAccountSubscription<
|
|
|
597
567
|
);
|
|
598
568
|
|
|
599
569
|
// Set callerStack on returned subscription after retrieval
|
|
600
|
-
if (callerStack
|
|
601
|
-
subscription.callerStack = callerStack
|
|
570
|
+
if (callerStack) {
|
|
571
|
+
subscription.callerStack = callerStack;
|
|
602
572
|
}
|
|
603
573
|
|
|
604
574
|
return {
|
|
@@ -759,27 +729,7 @@ export function useAccount<
|
|
|
759
729
|
},
|
|
760
730
|
): TSelectorReturn {
|
|
761
731
|
const subscription = useAccountSubscription(AccountSchema, options);
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
return useSyncExternalStoreWithSelector<
|
|
765
|
-
MaybeLoaded<Loaded<A, R>>,
|
|
766
|
-
TSelectorReturn
|
|
767
|
-
>(
|
|
768
|
-
React.useCallback(
|
|
769
|
-
(callback) => {
|
|
770
|
-
if (!subscription) {
|
|
771
|
-
return () => {};
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
return subscription.subscribe(callback);
|
|
775
|
-
},
|
|
776
|
-
[subscription],
|
|
777
|
-
),
|
|
778
|
-
getCurrentValue,
|
|
779
|
-
getCurrentValue,
|
|
780
|
-
options?.select ?? ((value) => value as TSelectorReturn),
|
|
781
|
-
options?.equalityFn ?? Object.is,
|
|
782
|
-
);
|
|
732
|
+
return useSubscriptionSelector(subscription, options);
|
|
783
733
|
}
|
|
784
734
|
|
|
785
735
|
export function useSuspenseAccount<
|
|
@@ -826,32 +776,7 @@ export function useSuspenseAccount<
|
|
|
826
776
|
|
|
827
777
|
use(subscription.getCachedPromise());
|
|
828
778
|
|
|
829
|
-
|
|
830
|
-
const value = subscription.getCurrentValue();
|
|
831
|
-
|
|
832
|
-
if (!value.$isLoaded) {
|
|
833
|
-
throw new Error("Account must be loaded in a suspense context");
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
return value;
|
|
837
|
-
};
|
|
838
|
-
|
|
839
|
-
return useSyncExternalStoreWithSelector<Loaded<A, R>, TSelectorReturn>(
|
|
840
|
-
React.useCallback(
|
|
841
|
-
(callback) => {
|
|
842
|
-
if (!subscription) {
|
|
843
|
-
return () => {};
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
return subscription.subscribe(callback);
|
|
847
|
-
},
|
|
848
|
-
[subscription],
|
|
849
|
-
),
|
|
850
|
-
getCurrentValue,
|
|
851
|
-
getCurrentValue,
|
|
852
|
-
options?.select ?? ((value) => value as TSelectorReturn),
|
|
853
|
-
options?.equalityFn ?? Object.is,
|
|
854
|
-
);
|
|
779
|
+
return useSubscriptionSelector(subscription, options);
|
|
855
780
|
}
|
|
856
781
|
|
|
857
782
|
/**
|
|
@@ -973,3 +898,232 @@ function getResolveQuery(
|
|
|
973
898
|
}
|
|
974
899
|
return true;
|
|
975
900
|
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Internal hook that suspends until all values are loaded.
|
|
904
|
+
*
|
|
905
|
+
* - Creates a Promise.all from individual getCachedPromise() calls
|
|
906
|
+
* - Returns Promise.resolve(null) for null subscriptions (undefined/null IDs)
|
|
907
|
+
* - Suspends via the use() hook until all values are loaded
|
|
908
|
+
*/
|
|
909
|
+
function useSuspendUntilLoaded(
|
|
910
|
+
subscriptions: (SubscriptionScope<CoValue> | null)[],
|
|
911
|
+
): void {
|
|
912
|
+
const combinedPromise = useMemo(() => {
|
|
913
|
+
const promises = subscriptions.map((sub) => {
|
|
914
|
+
if (!sub) {
|
|
915
|
+
// For null subscriptions (undefined/null IDs), resolve immediately with null
|
|
916
|
+
return Promise.resolve(null);
|
|
917
|
+
}
|
|
918
|
+
return sub.getCachedPromise();
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
return Promise.all(promises);
|
|
922
|
+
}, [subscriptions]);
|
|
923
|
+
|
|
924
|
+
use(combinedPromise);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Internal hook that uses useSyncExternalStore to subscribe to multiple SubscriptionScopes.
|
|
929
|
+
*
|
|
930
|
+
* - Creates a combined subscribe function that subscribes to all scopes
|
|
931
|
+
* - Returns an array of current values from each scope
|
|
932
|
+
* - Maintains stable references for unchanged values
|
|
933
|
+
*
|
|
934
|
+
* @param subscriptions - Array of SubscriptionScope instances (or null for skipped entries)
|
|
935
|
+
* @returns Array of loaded CoValues (or null for skipped entries)
|
|
936
|
+
*/
|
|
937
|
+
function useSubscriptionsSelector<
|
|
938
|
+
T extends CoValue[] | MaybeLoaded<CoValue>[],
|
|
939
|
+
// Selector input can be an already loaded or a maybe-loaded value,
|
|
940
|
+
// depending on whether a suspense hook is used or not, respectively.
|
|
941
|
+
TSelectorInput = T[number],
|
|
942
|
+
TSelectorReturn = TSelectorInput,
|
|
943
|
+
>(
|
|
944
|
+
subscriptions: SubscriptionScope<CoValue>[],
|
|
945
|
+
options?: {
|
|
946
|
+
select?: (value: TSelectorInput) => TSelectorReturn;
|
|
947
|
+
equalityFn?: (a: TSelectorReturn, b: TSelectorReturn) => boolean;
|
|
948
|
+
},
|
|
949
|
+
): TSelectorReturn[] {
|
|
950
|
+
// Combined subscribe function that subscribes to all scopes
|
|
951
|
+
const subscribe = useCallback(
|
|
952
|
+
(callback: () => void) => {
|
|
953
|
+
const unsubscribes = subscriptions.map((sub) => sub.subscribe(callback));
|
|
954
|
+
|
|
955
|
+
return () => {
|
|
956
|
+
unsubscribes.forEach((unsub) => unsub());
|
|
957
|
+
};
|
|
958
|
+
},
|
|
959
|
+
[subscriptions],
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
// Cache current values to avoid infinite loops
|
|
963
|
+
const cachedCurrentValuesRef = useRef<T>([] as unknown as T);
|
|
964
|
+
const getCurrentValues = useCallback(() => {
|
|
965
|
+
const newValues = subscriptions.map((sub) => sub.getCurrentValue());
|
|
966
|
+
|
|
967
|
+
// Check if values have changed by comparing each element
|
|
968
|
+
const cached = cachedCurrentValuesRef.current;
|
|
969
|
+
const hasChanged =
|
|
970
|
+
cached.length !== newValues.length ||
|
|
971
|
+
newValues.some((value, index) => value !== cached[index]);
|
|
972
|
+
|
|
973
|
+
if (hasChanged) {
|
|
974
|
+
cachedCurrentValuesRef.current = newValues as T;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return cachedCurrentValuesRef.current as unknown as TSelectorInput[];
|
|
978
|
+
}, [subscriptions]);
|
|
979
|
+
|
|
980
|
+
const selectFn = useMemo(() => {
|
|
981
|
+
if (!options?.select) {
|
|
982
|
+
return (values: TSelectorInput[]) =>
|
|
983
|
+
values as unknown as TSelectorReturn[];
|
|
984
|
+
}
|
|
985
|
+
return (values: TSelectorInput[]) =>
|
|
986
|
+
values.map((value) => options.select!(value));
|
|
987
|
+
}, [options?.select]);
|
|
988
|
+
|
|
989
|
+
const elementEqualityFn = useMemo(
|
|
990
|
+
() => options?.equalityFn ?? Object.is,
|
|
991
|
+
[options?.equalityFn],
|
|
992
|
+
);
|
|
993
|
+
const equalityFn = useMemo(() => {
|
|
994
|
+
return (a: TSelectorReturn[], b: TSelectorReturn[]) =>
|
|
995
|
+
a.length === b.length &&
|
|
996
|
+
a.every((value, index) => elementEqualityFn(value, b[index]));
|
|
997
|
+
}, [elementEqualityFn]);
|
|
998
|
+
|
|
999
|
+
return useSyncExternalStoreWithSelector(
|
|
1000
|
+
subscribe,
|
|
1001
|
+
getCurrentValues,
|
|
1002
|
+
getCurrentValues,
|
|
1003
|
+
selectFn,
|
|
1004
|
+
equalityFn,
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Subscribe to multiple CoValues with unified Suspense handling.
|
|
1010
|
+
*
|
|
1011
|
+
* This hook accepts a list of CoValue IDs and returns an array of loaded values,
|
|
1012
|
+
* suspending until all values are available.
|
|
1013
|
+
*
|
|
1014
|
+
* @param Schema - The CoValue schema or class constructor
|
|
1015
|
+
* @param ids - Array of CoValue IDs to subscribe to
|
|
1016
|
+
* @param options - Optional configuration, including resolve query
|
|
1017
|
+
* @returns An array of loaded CoValues in the same order as the input IDs
|
|
1018
|
+
*/
|
|
1019
|
+
export function useSuspenseCoStates<
|
|
1020
|
+
S extends CoValueClassOrSchema,
|
|
1021
|
+
// @ts-expect-error we can't statically enforce the schema's resolve query is a valid resolve query, but in practice it is
|
|
1022
|
+
const R extends ResolveQuery<S> = SchemaResolveQuery<S>,
|
|
1023
|
+
TSelectorReturn = Loaded<S, R>,
|
|
1024
|
+
>(
|
|
1025
|
+
Schema: S,
|
|
1026
|
+
ids: readonly string[],
|
|
1027
|
+
options?: {
|
|
1028
|
+
/** Resolve query to specify which nested CoValues to load */
|
|
1029
|
+
resolve?: ResolveQueryStrict<S, R>;
|
|
1030
|
+
/** Select which value to return. Applies to each element individually. */
|
|
1031
|
+
select?: (value: Loaded<S, R>) => TSelectorReturn;
|
|
1032
|
+
/** Equality function to determine if a selected value has changed, defaults to `Object.is` */
|
|
1033
|
+
equalityFn?: (a: TSelectorReturn, b: TSelectorReturn) => boolean;
|
|
1034
|
+
/**
|
|
1035
|
+
* Create or load a branch for isolated editing.
|
|
1036
|
+
*
|
|
1037
|
+
* Branching lets you take a snapshot of the current state and start modifying it without affecting the canonical/shared version.
|
|
1038
|
+
* It's a fork of your data graph: the same schema, but with diverging values.
|
|
1039
|
+
*
|
|
1040
|
+
* The checkout of the branch is applied on all the resolved values.
|
|
1041
|
+
*
|
|
1042
|
+
* @param name - A unique name for the branch. This identifies the branch
|
|
1043
|
+
* and can be used to switch between different branches of the same CoValue.
|
|
1044
|
+
* @param owner - The owner of the branch. Determines who can access and modify
|
|
1045
|
+
* the branch. If not provided, the branch is owned by the current user.
|
|
1046
|
+
*
|
|
1047
|
+
* For more info see the [branching](https://jazz.tools/docs/react/using-covalues/version-control) documentation.
|
|
1048
|
+
*/
|
|
1049
|
+
unstable_branch?: BranchDefinition;
|
|
1050
|
+
},
|
|
1051
|
+
): TSelectorReturn[] {
|
|
1052
|
+
const resolve = getResolveQuery(Schema, options?.resolve);
|
|
1053
|
+
const subscriptionScopes = useCoValueSubscriptions(
|
|
1054
|
+
Schema,
|
|
1055
|
+
ids,
|
|
1056
|
+
resolve,
|
|
1057
|
+
options?.unstable_branch,
|
|
1058
|
+
) as SubscriptionScope<CoValue>[];
|
|
1059
|
+
useSuspendUntilLoaded(subscriptionScopes);
|
|
1060
|
+
return useSubscriptionsSelector(subscriptionScopes, options);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Subscribe to multiple CoValues without Suspense.
|
|
1065
|
+
*
|
|
1066
|
+
* This hook accepts a list of CoValue IDs and returns an array of maybe-loaded values.
|
|
1067
|
+
* Unlike `useSuspenseCoStates`, this hook does not suspend and returns loading/unavailable
|
|
1068
|
+
* states that can be checked via the `$isLoaded` property.
|
|
1069
|
+
*
|
|
1070
|
+
* @param Schema - The CoValue schema or class constructor
|
|
1071
|
+
* @param ids - Array of CoValue IDs to subscribe to
|
|
1072
|
+
* @param options - Optional configuration, including resolve query
|
|
1073
|
+
* @returns An array of MaybeLoaded CoValues in the same order as the input IDs
|
|
1074
|
+
*
|
|
1075
|
+
* @example
|
|
1076
|
+
* ```typescript
|
|
1077
|
+
* const [project1, project2] = useCoStates(
|
|
1078
|
+
* ProjectSchema,
|
|
1079
|
+
* [projectId1, projectId2],
|
|
1080
|
+
* { resolve: { assignee: true } }
|
|
1081
|
+
* );
|
|
1082
|
+
*
|
|
1083
|
+
* if (!project1.$isLoaded || !project2.$isLoaded) {
|
|
1084
|
+
* return <Loading />;
|
|
1085
|
+
* }
|
|
1086
|
+
* ```
|
|
1087
|
+
*/
|
|
1088
|
+
export function useCoStates<
|
|
1089
|
+
S extends CoValueClassOrSchema,
|
|
1090
|
+
// @ts-expect-error we can't statically enforce the schema's resolve query is a valid resolve query, but in practice it is
|
|
1091
|
+
const R extends ResolveQuery<S> = SchemaResolveQuery<S>,
|
|
1092
|
+
TSelectorReturn = MaybeLoaded<Loaded<S, R>>,
|
|
1093
|
+
>(
|
|
1094
|
+
Schema: S,
|
|
1095
|
+
ids: readonly string[],
|
|
1096
|
+
options?: {
|
|
1097
|
+
/** Resolve query to specify which nested CoValues to load */
|
|
1098
|
+
resolve?: ResolveQueryStrict<S, R>;
|
|
1099
|
+
/** Select which value to return. Applies to each element individually. */
|
|
1100
|
+
select?: (value: MaybeLoaded<Loaded<S, R>>) => TSelectorReturn;
|
|
1101
|
+
/** Equality function to determine if a selected value has changed, defaults to `Object.is` */
|
|
1102
|
+
equalityFn?: (a: TSelectorReturn, b: TSelectorReturn) => boolean;
|
|
1103
|
+
/**
|
|
1104
|
+
* Create or load a branch for isolated editing.
|
|
1105
|
+
*
|
|
1106
|
+
* Branching lets you take a snapshot of the current state and start modifying it without affecting the canonical/shared version.
|
|
1107
|
+
* It's a fork of your data graph: the same schema, but with diverging values.
|
|
1108
|
+
*
|
|
1109
|
+
* The checkout of the branch is applied on all the resolved values.
|
|
1110
|
+
*
|
|
1111
|
+
* @param name - A unique name for the branch. This identifies the branch
|
|
1112
|
+
* and can be used to switch between different branches of the same CoValue.
|
|
1113
|
+
* @param owner - The owner of the branch. Determines who can access and modify
|
|
1114
|
+
* the branch. If not provided, the branch is owned by the current user.
|
|
1115
|
+
*
|
|
1116
|
+
* For more info see the [branching](https://jazz.tools/docs/react/using-covalues/version-control) documentation.
|
|
1117
|
+
*/
|
|
1118
|
+
unstable_branch?: BranchDefinition;
|
|
1119
|
+
},
|
|
1120
|
+
): TSelectorReturn[] {
|
|
1121
|
+
const resolve = getResolveQuery(Schema, options?.resolve);
|
|
1122
|
+
const subscriptionScopes = useCoValueSubscriptions(
|
|
1123
|
+
Schema,
|
|
1124
|
+
ids,
|
|
1125
|
+
resolve,
|
|
1126
|
+
options?.unstable_branch,
|
|
1127
|
+
) as SubscriptionScope<CoValue>[];
|
|
1128
|
+
return useSubscriptionsSelector(subscriptionScopes, options);
|
|
1129
|
+
}
|