on-zero 0.4.22 → 0.4.24
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/cjs/combineZeroClients.cjs +101 -0
- package/dist/cjs/combineZeroClients.native.js +150 -0
- package/dist/cjs/combineZeroClients.native.js.map +1 -0
- package/dist/cjs/createUseQuery.cjs +92 -4
- package/dist/cjs/createUseQuery.native.js +130 -4
- package/dist/cjs/createUseQuery.native.js.map +1 -1
- package/dist/cjs/createZeroClient.cjs +89 -12
- package/dist/cjs/createZeroClient.native.js +134 -43
- package/dist/cjs/createZeroClient.native.js.map +1 -1
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.native.js +1 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/instanceRegistry.cjs +65 -0
- package/dist/cjs/instanceRegistry.native.js +111 -0
- package/dist/cjs/instanceRegistry.native.js.map +1 -0
- package/dist/cjs/multiInstance.test.cjs +322 -0
- package/dist/cjs/multiInstance.test.native.js +387 -0
- package/dist/cjs/multiInstance.test.native.js.map +1 -0
- package/dist/cjs/multiInstanceNested.test.cjs +206 -0
- package/dist/cjs/multiInstanceNested.test.native.js +254 -0
- package/dist/cjs/multiInstanceNested.test.native.js.map +1 -0
- package/dist/cjs/run.cjs +5 -5
- package/dist/cjs/run.native.js +6 -5
- package/dist/cjs/run.native.js.map +1 -1
- package/dist/cjs/zeroRunner.cjs +4 -1
- package/dist/cjs/zeroRunner.native.js +4 -1
- package/dist/cjs/zeroRunner.native.js.map +1 -1
- package/dist/esm/combineZeroClients.mjs +76 -0
- package/dist/esm/combineZeroClients.mjs.map +1 -0
- package/dist/esm/combineZeroClients.native.js +122 -0
- package/dist/esm/combineZeroClients.native.js.map +1 -0
- package/dist/esm/createUseQuery.mjs +92 -5
- package/dist/esm/createUseQuery.mjs.map +1 -1
- package/dist/esm/createUseQuery.native.js +130 -5
- package/dist/esm/createUseQuery.native.js.map +1 -1
- package/dist/esm/createZeroClient.mjs +93 -16
- package/dist/esm/createZeroClient.mjs.map +1 -1
- package/dist/esm/createZeroClient.native.js +138 -47
- package/dist/esm/createZeroClient.native.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +1 -0
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/instanceRegistry.mjs +38 -0
- package/dist/esm/instanceRegistry.mjs.map +1 -0
- package/dist/esm/instanceRegistry.native.js +81 -0
- package/dist/esm/instanceRegistry.native.js.map +1 -0
- package/dist/esm/multiInstance.test.mjs +323 -0
- package/dist/esm/multiInstance.test.mjs.map +1 -0
- package/dist/esm/multiInstance.test.native.js +385 -0
- package/dist/esm/multiInstance.test.native.js.map +1 -0
- package/dist/esm/multiInstanceNested.test.mjs +207 -0
- package/dist/esm/multiInstanceNested.test.mjs.map +1 -0
- package/dist/esm/multiInstanceNested.test.native.js +252 -0
- package/dist/esm/multiInstanceNested.test.native.js.map +1 -0
- package/dist/esm/run.mjs +5 -5
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/run.native.js +6 -5
- package/dist/esm/run.native.js.map +1 -1
- package/dist/esm/zeroRunner.mjs +4 -1
- package/dist/esm/zeroRunner.mjs.map +1 -1
- package/dist/esm/zeroRunner.native.js +4 -1
- package/dist/esm/zeroRunner.native.js.map +1 -1
- package/package.json +5 -3
- package/readme.md +59 -0
- package/src/combineZeroClients.tsx +186 -0
- package/src/createUseQuery.tsx +175 -12
- package/src/createZeroClient.tsx +227 -54
- package/src/index.ts +1 -0
- package/src/instanceRegistry.ts +75 -0
- package/src/multiInstance.test.tsx +284 -0
- package/src/multiInstanceNested.test.tsx +205 -0
- package/src/run.ts +7 -6
- package/src/zeroRunner.ts +7 -1
- package/types/combineZeroClients.d.ts +38 -0
- package/types/combineZeroClients.d.ts.map +1 -0
- package/types/createUseQuery.d.ts +15 -0
- package/types/createUseQuery.d.ts.map +1 -1
- package/types/createZeroClient.d.ts +10 -4
- package/types/createZeroClient.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/instanceRegistry.d.ts +15 -0
- package/types/instanceRegistry.d.ts.map +1 -0
- package/types/multiInstance.test.d.ts +2 -0
- package/types/multiInstance.test.d.ts.map +1 -0
- package/types/multiInstanceNested.test.d.ts +5 -0
- package/types/multiInstanceNested.test.d.ts.map +1 -0
- package/types/run.d.ts.map +1 -1
- package/types/zeroRunner.d.ts +3 -1
- package/types/zeroRunner.d.ts.map +1 -1
package/src/createZeroClient.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { defineQueries, defineQuery } from '@rocicorp/zero'
|
|
1
|
+
import { defineQueries, defineQuery, Zero as ZeroClient } from '@rocicorp/zero'
|
|
2
2
|
import { useConnectionState, useZero, ZeroProvider } from '@rocicorp/zero/react'
|
|
3
|
-
import { createEmitter } from '@take-out/helpers'
|
|
3
|
+
import { createEmitter, useEmitterValue } from '@take-out/helpers'
|
|
4
4
|
import {
|
|
5
5
|
createContext,
|
|
6
6
|
memo,
|
|
@@ -8,11 +8,18 @@ import {
|
|
|
8
8
|
useEffect,
|
|
9
9
|
useMemo,
|
|
10
10
|
useRef,
|
|
11
|
+
useState,
|
|
11
12
|
type ReactNode,
|
|
12
13
|
} from 'react'
|
|
13
14
|
|
|
14
15
|
import { createPermissions } from './createPermissions'
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
createUseQuery,
|
|
18
|
+
createUseQueryDirect,
|
|
19
|
+
type QueryControlMode,
|
|
20
|
+
type UseQueryHook,
|
|
21
|
+
} from './createUseQuery'
|
|
22
|
+
import { registerClientInstance } from './instanceRegistry'
|
|
16
23
|
import { createMutators } from './helpers/createMutators'
|
|
17
24
|
import { getAuth } from './helpers/getAuth'
|
|
18
25
|
import { getAllMutationsPermissions, getMutationsPermissions } from './modelRegistry'
|
|
@@ -21,7 +28,7 @@ import { resolveQuery, type PlainQueryFn } from './resolveQuery'
|
|
|
21
28
|
import { setCustomQueries } from './run'
|
|
22
29
|
import { getEnvironment, setAuthData, setEnvironment, setSchema } from './state'
|
|
23
30
|
import { getRawWhere, setEvaluatingPermission } from './where'
|
|
24
|
-
import { setRunner } from './zeroRunner'
|
|
31
|
+
import { setRunner, type ZeroRunner } from './zeroRunner'
|
|
25
32
|
import { zql } from './zql'
|
|
26
33
|
|
|
27
34
|
import type { AuthData, GenericModels, GetZeroMutators, ZeroEvent } from './types'
|
|
@@ -62,11 +69,16 @@ export function createZeroClient<
|
|
|
62
69
|
models,
|
|
63
70
|
groupedQueries,
|
|
64
71
|
permissionStrategy = 'optimistic',
|
|
72
|
+
instanceName = 'default',
|
|
65
73
|
}: {
|
|
66
74
|
schema: Schema
|
|
67
75
|
models: Models
|
|
68
76
|
groupedQueries: GroupedQueries
|
|
69
77
|
permissionStrategy?: PermissionStrategy
|
|
78
|
+
// names this client instance so multiple instances can coexist on one page.
|
|
79
|
+
// each query/mutator namespace is claimed by exactly one instance, and the
|
|
80
|
+
// ambient run() + the combineZeroClients facade dispatch by that claim.
|
|
81
|
+
instanceName?: string
|
|
70
82
|
}) {
|
|
71
83
|
type ZeroMutators = GetZeroMutators<Models>
|
|
72
84
|
type ZeroInstance = Zero<Schema, ZeroMutators>
|
|
@@ -176,6 +188,15 @@ export function createZeroClient<
|
|
|
176
188
|
// create the single shared CustomQuery registry
|
|
177
189
|
const customQueries = defineQueries(wrappedNamespaces)
|
|
178
190
|
|
|
191
|
+
// claim this instance's query/mutator namespaces so the ambient run() and
|
|
192
|
+
// the combineZeroClients facade dispatch to the owning instance. the
|
|
193
|
+
// auto-generated 'permission' namespace stays unclaimed (per-instance).
|
|
194
|
+
const instance = registerClientInstance({
|
|
195
|
+
name: instanceName,
|
|
196
|
+
namespaces: Object.keys({ ...groupedQueries, ...models }),
|
|
197
|
+
customQueries,
|
|
198
|
+
})
|
|
199
|
+
|
|
179
200
|
// register for global run() helper
|
|
180
201
|
setCustomQueries(customQueries)
|
|
181
202
|
|
|
@@ -196,7 +217,20 @@ export function createZeroClient<
|
|
|
196
217
|
},
|
|
197
218
|
})
|
|
198
219
|
|
|
199
|
-
|
|
220
|
+
// emitter names are global keys (dev hmr cache) — scope them per instance
|
|
221
|
+
// so two instances never share cached values. the default name stays
|
|
222
|
+
// unchanged for single-instance back-compat.
|
|
223
|
+
const emitterScope = instanceName === 'default' ? '' : `:${instanceName}`
|
|
224
|
+
|
|
225
|
+
const zeroEvents = createEmitter<ZeroEvent | null>(`zero${emitterScope}`, null)
|
|
226
|
+
|
|
227
|
+
// bumped after commit whenever the mounted zero instance changes (first
|
|
228
|
+
// mount, identity change, client-state-not-found rotation) — the direct
|
|
229
|
+
// query path re-materializes its views against the new instance
|
|
230
|
+
const zeroInstanceVersion = createEmitter<number>(
|
|
231
|
+
`zero-instance-version${emitterScope}`,
|
|
232
|
+
0,
|
|
233
|
+
)
|
|
200
234
|
|
|
201
235
|
const AuthDataContext = createContext<AuthData>({} as AuthData)
|
|
202
236
|
|
|
@@ -205,50 +239,93 @@ export function createZeroClient<
|
|
|
205
239
|
customQueries,
|
|
206
240
|
})
|
|
207
241
|
|
|
242
|
+
// context-free counterpart: materializes directly on THIS instance's
|
|
243
|
+
// mounted zero instead of the nearest ZeroProvider context. used by
|
|
244
|
+
// combineZeroClients for instances whose provider is not innermost.
|
|
245
|
+
const useQueryDirect = createUseQueryDirect<Schema>({
|
|
246
|
+
DisabledContext,
|
|
247
|
+
customQueries,
|
|
248
|
+
getZero: () => latestZeroInstance,
|
|
249
|
+
zeroVersion: zeroInstanceVersion,
|
|
250
|
+
})
|
|
251
|
+
|
|
208
252
|
// permission check uses a per-model synced query so server is authoritative
|
|
209
|
-
// permissionStrategy controls client behavior before server responds
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
253
|
+
// permissionStrategy controls client behavior before server responds.
|
|
254
|
+
// built over a query hook so the facade can route permission checks down
|
|
255
|
+
// the same context vs direct path as the table's other queries.
|
|
256
|
+
const createUsePermission = (useQueryImpl: UseQueryHook<Schema>) =>
|
|
257
|
+
function usePermission(
|
|
258
|
+
table: TableName | (string & {}),
|
|
259
|
+
objOrId: string | Partial<Row<any>> | undefined,
|
|
260
|
+
enabled = typeof objOrId !== 'undefined',
|
|
261
|
+
debug = false,
|
|
262
|
+
): boolean | null {
|
|
263
|
+
const disableMode = useContext(DisabledContext)
|
|
264
|
+
const lastRef = useRef<boolean | null>(null)
|
|
265
|
+
const tableStr = table as string
|
|
266
|
+
const checkFn = permissionCheckFns[tableStr]
|
|
267
|
+
|
|
268
|
+
// include auth user ID in query args so zero-cache creates per-user
|
|
269
|
+
// permission views (prevents dedup across different auth contexts)
|
|
270
|
+
const auth = getAuth()
|
|
271
|
+
const _uid = auth?.id || 'anon'
|
|
272
|
+
|
|
273
|
+
const [data, status] = useQueryImpl(
|
|
274
|
+
checkFn as any,
|
|
275
|
+
{ objOrId: objOrId as any, _uid },
|
|
276
|
+
{ enabled: Boolean(!disableMode && enabled && objOrId && checkFn) },
|
|
277
|
+
)
|
|
231
278
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
279
|
+
if (debug) {
|
|
280
|
+
console.info(`usePermission()`, { table, objOrId, data, status })
|
|
281
|
+
}
|
|
235
282
|
|
|
236
|
-
|
|
283
|
+
if (!objOrId) return false
|
|
237
284
|
|
|
238
|
-
|
|
239
|
-
|
|
285
|
+
// null while loading, then server's authoritative answer
|
|
286
|
+
const result = status.type === 'unknown' ? null : Boolean(data)
|
|
240
287
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
288
|
+
if (!disableMode) {
|
|
289
|
+
lastRef.current = result
|
|
290
|
+
return result
|
|
291
|
+
}
|
|
245
292
|
|
|
246
|
-
|
|
247
|
-
|
|
293
|
+
if (disableMode === 'last-value') {
|
|
294
|
+
return lastRef.current
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return null
|
|
248
298
|
}
|
|
249
299
|
|
|
250
|
-
|
|
251
|
-
|
|
300
|
+
const usePermission = createUsePermission(useQuery)
|
|
301
|
+
const usePermissionDirect = createUsePermission(useQueryDirect)
|
|
302
|
+
|
|
303
|
+
// the zero instance lives OUTSIDE the react lifecycle. react destroys and
|
|
304
|
+
// re-fires the effects of a committed tree on any suspense hide/reveal, and
|
|
305
|
+
// consumers also remount the provider (splash -> IDE). when ZeroProvider
|
|
306
|
+
// created zero inside its own effect, every such cycle closed the live
|
|
307
|
+
// instance mid-connect ("Failed to connect / Store is closed") and built a
|
|
308
|
+
// replacement, killing in-flight queries and preloads. instead the instance
|
|
309
|
+
// is created in an effect, cached here per identity key, handed to
|
|
310
|
+
// ZeroProvider as an external `zero` (which it never closes), and the
|
|
311
|
+
// previous instance is closed only when the key truly changes — a real
|
|
312
|
+
// identity change (user, storage, server, logged in/out), never a react
|
|
313
|
+
// lifecycle artifact. single-provider assumption: one mounted ProvideZero
|
|
314
|
+
// per client (true of every consumer); two simultaneously mounted providers
|
|
315
|
+
// with different identities would thrash this slot.
|
|
316
|
+
let cachedZero: { key: string; instance: ZeroInstance } | null = null
|
|
317
|
+
|
|
318
|
+
// bumping this recreates the instance with the same options — parity with
|
|
319
|
+
// ZeroProvider's built-in onClientStateNotFound rotation. the pending flag
|
|
320
|
+
// mirrors upstream's rotationPendingRef: a storm of client-state-not-found
|
|
321
|
+
// callbacks coalesces into ONE rotation, re-armed when the fresh instance
|
|
322
|
+
// is created — without it a new instance that immediately re-fires the
|
|
323
|
+
// callback rotation-loops forever.
|
|
324
|
+
const zeroInstanceGeneration = createEmitter<number>(
|
|
325
|
+
`zero-instance-generation${emitterScope}`,
|
|
326
|
+
0,
|
|
327
|
+
)
|
|
328
|
+
let zeroRotationPending = false
|
|
252
329
|
|
|
253
330
|
const ProvideZero = ({
|
|
254
331
|
children,
|
|
@@ -279,6 +356,93 @@ export function createZeroClient<
|
|
|
279
356
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
280
357
|
}, [])
|
|
281
358
|
|
|
359
|
+
const generation = useEmitterValue(zeroInstanceGeneration)
|
|
360
|
+
|
|
361
|
+
const auth = 'auth' in props ? (props as { auth?: string | null }).auth : undefined
|
|
362
|
+
const hasAuth = typeof auth === 'string'
|
|
363
|
+
|
|
364
|
+
// identity = every primitive option except the auth token value (token
|
|
365
|
+
// changes refresh in place below; logged-in <-> logged-out still rotates
|
|
366
|
+
// via hasAuth, matching zero's documented provider semantics). function
|
|
367
|
+
// props (callbacks, logSink, batchViewUpdates) are bound at construction.
|
|
368
|
+
const instanceKey = JSON.stringify([
|
|
369
|
+
Object.entries({ kvStore: 'mem', ...props })
|
|
370
|
+
.filter(
|
|
371
|
+
([key, value]) =>
|
|
372
|
+
key !== 'auth' && typeof value !== 'function' && value !== undefined,
|
|
373
|
+
)
|
|
374
|
+
.sort(([a], [b]) => (a < b ? -1 : 1)),
|
|
375
|
+
hasAuth,
|
|
376
|
+
generation,
|
|
377
|
+
])
|
|
378
|
+
|
|
379
|
+
// create/rotate in an effect — commit-safe: a discarded concurrent render
|
|
380
|
+
// can never close an instance the committed tree still uses. a suspense
|
|
381
|
+
// hide/reveal re-runs this effect with an unchanged key — cache hit, no
|
|
382
|
+
// churn, no cleanup-close. children mounting one effect-tick after the
|
|
383
|
+
// provider matches ZeroProvider's internal-creation timing, which consumer
|
|
384
|
+
// boot orchestration observes.
|
|
385
|
+
const [instance, setInstance] = useState<ZeroInstance | undefined>()
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
let cached = cachedZero
|
|
388
|
+
if (cached?.key !== instanceKey) {
|
|
389
|
+
cached?.instance.close()
|
|
390
|
+
const options = props as Omit<
|
|
391
|
+
ZeroOptions<Schema, ZeroMutators>,
|
|
392
|
+
'schema' | 'mutators'
|
|
393
|
+
>
|
|
394
|
+
cached = {
|
|
395
|
+
key: instanceKey,
|
|
396
|
+
instance: new ZeroClient<Schema, ZeroMutators>({
|
|
397
|
+
kvStore: 'mem',
|
|
398
|
+
...options,
|
|
399
|
+
schema,
|
|
400
|
+
// @ts-expect-error same erasure ZeroProvider needed
|
|
401
|
+
mutators,
|
|
402
|
+
onClientStateNotFound: () => {
|
|
403
|
+
// consumer callback wins; recreate-with-same-options is the
|
|
404
|
+
// fallback, mirroring ZeroProvider's internal rotation.
|
|
405
|
+
if (options.onClientStateNotFound) {
|
|
406
|
+
try {
|
|
407
|
+
options.onClientStateNotFound()
|
|
408
|
+
return
|
|
409
|
+
} catch {
|
|
410
|
+
// fall through to rotation
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (zeroRotationPending) return
|
|
414
|
+
zeroRotationPending = true
|
|
415
|
+
zeroInstanceGeneration.emit(zeroInstanceGeneration.value + 1)
|
|
416
|
+
},
|
|
417
|
+
}),
|
|
418
|
+
}
|
|
419
|
+
cachedZero = cached
|
|
420
|
+
zeroRotationPending = false
|
|
421
|
+
}
|
|
422
|
+
setInstance(cached.instance)
|
|
423
|
+
// identity is fully captured by instanceKey; function props are bound
|
|
424
|
+
// at construction by design.
|
|
425
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
426
|
+
}, [instanceKey])
|
|
427
|
+
|
|
428
|
+
// a changed token on the same identity refreshes auth in place — zero
|
|
429
|
+
// sends an auth update over the live connection instead of reconnecting
|
|
430
|
+
// (upstream ZeroProvider does exactly this). string <-> undefined flips
|
|
431
|
+
// rotate the instance via hasAuth in the identity key instead.
|
|
432
|
+
const prevAuthRef = useRef(auth)
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
const prevAuth = prevAuthRef.current
|
|
435
|
+
prevAuthRef.current = auth
|
|
436
|
+
if (
|
|
437
|
+
instance &&
|
|
438
|
+
typeof prevAuth === 'string' &&
|
|
439
|
+
typeof auth === 'string' &&
|
|
440
|
+
prevAuth !== auth
|
|
441
|
+
) {
|
|
442
|
+
instance.connection.connect({ auth })
|
|
443
|
+
}
|
|
444
|
+
}, [instance, auth])
|
|
445
|
+
|
|
282
446
|
// for now we re-parent
|
|
283
447
|
if (disable) {
|
|
284
448
|
return children
|
|
@@ -286,17 +450,13 @@ export function createZeroClient<
|
|
|
286
450
|
|
|
287
451
|
return (
|
|
288
452
|
<AuthDataContext.Provider value={authData}>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
<SetZeroInstance />
|
|
297
|
-
<ConnectionMonitor zeroEvents={zeroEvents} />
|
|
298
|
-
{children}
|
|
299
|
-
</ZeroProvider>
|
|
453
|
+
{instance ? (
|
|
454
|
+
<ZeroProvider zero={instance}>
|
|
455
|
+
<SetZeroInstance />
|
|
456
|
+
<ConnectionMonitor zeroEvents={zeroEvents} />
|
|
457
|
+
{children}
|
|
458
|
+
</ZeroProvider>
|
|
459
|
+
) : null}
|
|
300
460
|
</AuthDataContext.Provider>
|
|
301
461
|
)
|
|
302
462
|
}
|
|
@@ -312,10 +472,20 @@ export function createZeroClient<
|
|
|
312
472
|
// we'll need to add that soon
|
|
313
473
|
if (zeroInstance !== latestZeroInstance) {
|
|
314
474
|
latestZeroInstance = zeroInstance
|
|
315
|
-
|
|
316
|
-
|
|
475
|
+
const runner: ZeroRunner = (query, options) =>
|
|
476
|
+
zeroInstance.run(query as any, options)
|
|
477
|
+
// the instance-keyed runner is what run() dispatches owned namespaces
|
|
478
|
+
// to; the global runner stays as the ambient fallback (inline zql)
|
|
479
|
+
instance.runner = runner
|
|
480
|
+
setRunner(runner)
|
|
317
481
|
}
|
|
318
482
|
|
|
483
|
+
// notify direct-path views after commit (emitting during render would
|
|
484
|
+
// setState other components mid-render)
|
|
485
|
+
useEffect(() => {
|
|
486
|
+
zeroInstanceVersion.emit(zeroInstanceVersion.value + 1)
|
|
487
|
+
}, [zeroInstance])
|
|
488
|
+
|
|
319
489
|
return null
|
|
320
490
|
}
|
|
321
491
|
|
|
@@ -398,11 +568,14 @@ export function createZeroClient<
|
|
|
398
568
|
}
|
|
399
569
|
|
|
400
570
|
return {
|
|
571
|
+
instanceName,
|
|
401
572
|
zeroEvents,
|
|
402
573
|
ProvideZero,
|
|
403
574
|
ControlQueries,
|
|
404
575
|
useQuery,
|
|
576
|
+
useQueryDirect,
|
|
405
577
|
usePermission,
|
|
578
|
+
usePermissionDirect,
|
|
406
579
|
zero,
|
|
407
580
|
preload,
|
|
408
581
|
getQuery,
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './helpers/useMutation'
|
|
|
8
8
|
export { ensureAuth, getAuth } from './helpers/getAuth'
|
|
9
9
|
export { setAuthData, setEnvironment } from './state'
|
|
10
10
|
|
|
11
|
+
export * from './combineZeroClients'
|
|
11
12
|
export * from './createZeroClient'
|
|
12
13
|
export * from './createUseQuery'
|
|
13
14
|
export * from './resolveQuery'
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// registry mapping query/mutator namespaces to the zero client instance that
|
|
2
|
+
// owns them. enables multiple createZeroClient instances on one page (e.g. a
|
|
3
|
+
// control-plane instance + a per-project instance): the ambient run() and the
|
|
4
|
+
// combineZeroClients facade dispatch each query/mutation to the owning
|
|
5
|
+
// instance by its registered namespace instead of whichever mounted last.
|
|
6
|
+
//
|
|
7
|
+
// stored via globalValue so dual-loaded module copies (cjs/esm) share one map,
|
|
8
|
+
// matching the package's other cross-module registries.
|
|
9
|
+
|
|
10
|
+
import { globalValue } from '@take-out/helpers'
|
|
11
|
+
|
|
12
|
+
import { getQueryName } from './queryRegistry'
|
|
13
|
+
|
|
14
|
+
import type { ZeroRunner } from './zeroRunner'
|
|
15
|
+
import type { AnyQueryRegistry } from '@rocicorp/zero'
|
|
16
|
+
|
|
17
|
+
export type ZeroClientInstance = {
|
|
18
|
+
name: string
|
|
19
|
+
customQueries: AnyQueryRegistry
|
|
20
|
+
// set when the instance's provider mounts (client); stays null on the
|
|
21
|
+
// server, where the ambient transaction runner serves every instance
|
|
22
|
+
runner: ZeroRunner | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const getInstancesByNamespace = () =>
|
|
26
|
+
globalValue<Map<string, ZeroClientInstance>>(
|
|
27
|
+
'on-zero:instances-by-namespace',
|
|
28
|
+
() => new Map(),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export function registerClientInstance({
|
|
32
|
+
name,
|
|
33
|
+
namespaces,
|
|
34
|
+
customQueries,
|
|
35
|
+
}: {
|
|
36
|
+
name: string
|
|
37
|
+
namespaces: string[]
|
|
38
|
+
customQueries: AnyQueryRegistry
|
|
39
|
+
}): ZeroClientInstance {
|
|
40
|
+
const instancesByNamespace = getInstancesByNamespace()
|
|
41
|
+
|
|
42
|
+
// re-creating an instance under the same name (hmr) replaces its claims
|
|
43
|
+
for (const [namespace, owner] of instancesByNamespace) {
|
|
44
|
+
if (owner.name === name) {
|
|
45
|
+
instancesByNamespace.delete(namespace)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const instance: ZeroClientInstance = { name, customQueries, runner: null }
|
|
50
|
+
|
|
51
|
+
for (const namespace of namespaces) {
|
|
52
|
+
const existing = instancesByNamespace.get(namespace)
|
|
53
|
+
if (existing) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`[on-zero] namespace '${namespace}' is already claimed by zero client instance '${existing.name}' ` +
|
|
56
|
+
`(while creating instance '${name}'). Each query/mutator namespace must belong to exactly one createZeroClient instance.`,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
instancesByNamespace.set(namespace, instance)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return instance
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getInstanceForNamespace(
|
|
66
|
+
namespace: string,
|
|
67
|
+
): ZeroClientInstance | undefined {
|
|
68
|
+
return getInstancesByNamespace().get(namespace)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getInstanceForQueryFn(fn: Function): ZeroClientInstance | undefined {
|
|
72
|
+
const queryName = getQueryName(fn)
|
|
73
|
+
if (!queryName) return undefined
|
|
74
|
+
return getInstanceForNamespace(queryName.split('.', 1)[0])
|
|
75
|
+
}
|