on-zero 0.9.2 → 0.9.4
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/createZeroClient.cjs +176 -100
- package/dist/cjs/createZeroClient.connection.test.cjs +32 -0
- package/dist/cjs/createZeroClient.connection.test.native.js +34 -0
- package/dist/cjs/createZeroClient.connection.test.native.js.map +1 -1
- package/dist/cjs/createZeroClient.headless.test.cjs +33 -0
- package/dist/cjs/createZeroClient.headless.test.native.js +38 -0
- package/dist/cjs/createZeroClient.headless.test.native.js.map +1 -0
- package/dist/cjs/createZeroClient.native.js +207 -132
- package/dist/cjs/createZeroClient.native.js.map +1 -1
- package/dist/cjs/zeroRunner.cjs +1 -1
- package/dist/cjs/zeroRunner.native.js +1 -1
- package/dist/esm/createZeroClient.connection.test.mjs +32 -0
- package/dist/esm/createZeroClient.connection.test.mjs.map +1 -1
- package/dist/esm/createZeroClient.connection.test.native.js +34 -0
- package/dist/esm/createZeroClient.connection.test.native.js.map +1 -1
- package/dist/esm/createZeroClient.headless.test.mjs +34 -0
- package/dist/esm/createZeroClient.headless.test.mjs.map +1 -0
- package/dist/esm/createZeroClient.headless.test.native.js +36 -0
- package/dist/esm/createZeroClient.headless.test.native.js.map +1 -0
- package/dist/esm/createZeroClient.mjs +178 -102
- package/dist/esm/createZeroClient.mjs.map +1 -1
- package/dist/esm/createZeroClient.native.js +209 -134
- package/dist/esm/createZeroClient.native.js.map +1 -1
- package/dist/esm/zeroRunner.mjs +1 -1
- package/dist/esm/zeroRunner.mjs.map +1 -1
- package/dist/esm/zeroRunner.native.js +1 -1
- package/package.json +1 -1
- package/src/createZeroClient.connection.test.tsx +38 -0
- package/src/createZeroClient.headless.test.tsx +47 -0
- package/src/createZeroClient.tsx +323 -218
- package/src/zeroRunner.ts +1 -1
- package/types/createZeroClient.d.ts +24 -0
- package/types/createZeroClient.d.ts.map +1 -1
- package/types/createZeroClient.headless.test.d.ts +2 -0
- package/types/createZeroClient.headless.test.d.ts.map +1 -0
package/src/createZeroClient.tsx
CHANGED
|
@@ -291,9 +291,116 @@ export function createZeroClientInternal<
|
|
|
291
291
|
|
|
292
292
|
const DisabledContext = createContext<QueryControlMode>(false)
|
|
293
293
|
|
|
294
|
+
// mutators never vary per mount: auth is read dynamically through
|
|
295
|
+
// getAuthData() at mutation time. built once, lazily, so the provider and
|
|
296
|
+
// connectHeadless construct identical instances without either one forcing
|
|
297
|
+
// the work at createZeroClient() time.
|
|
298
|
+
let clientMutators: ReturnType<typeof createMutators> | null = null
|
|
299
|
+
function getClientMutators() {
|
|
300
|
+
clientMutators ??= createMutators({
|
|
301
|
+
models,
|
|
302
|
+
environment: 'client',
|
|
303
|
+
authData: null,
|
|
304
|
+
can: permissionsHelpers.can,
|
|
305
|
+
})
|
|
306
|
+
return clientMutators
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
type ConstructZeroInstanceArgs = {
|
|
310
|
+
options: Omit<ZeroOptions<Schema, ZeroMutators>, 'schema' | 'mutators'>
|
|
311
|
+
transport?: ZeroProviderTransport
|
|
312
|
+
beforeReload?: () => Promise<void>
|
|
313
|
+
scheduleReload?: (ctx: ScheduleReloadContext) => void
|
|
314
|
+
guardStorage?: RecoveryGuardStorage
|
|
315
|
+
benignLogPatterns?: readonly ZeroLogPattern[]
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// build a Zero instance with on-zero's recovery wiring. shared by the
|
|
319
|
+
// provider's rotation effect and by connectHeadless so a non-react host gets
|
|
320
|
+
// the same instance the app gets, rather than a parallel construction that
|
|
321
|
+
// can drift.
|
|
322
|
+
function constructZeroInstance({
|
|
323
|
+
options,
|
|
324
|
+
transport,
|
|
325
|
+
beforeReload,
|
|
326
|
+
scheduleReload,
|
|
327
|
+
guardStorage,
|
|
328
|
+
benignLogPatterns,
|
|
329
|
+
}: ConstructZeroInstanceArgs): ZeroInstance {
|
|
330
|
+
// install before construction so the instance's first connect goes through
|
|
331
|
+
// HTTP. ensureHttpPullTransport is per-origin idempotent by design (a
|
|
332
|
+
// rotation would otherwise chain shims), so repeat calls reuse.
|
|
333
|
+
if (transport) {
|
|
334
|
+
// same precedence as zero's own getServer (cacheURL is the current
|
|
335
|
+
// option name; server is its deprecated alias)
|
|
336
|
+
const serverURL = options.cacheURL ?? options.server
|
|
337
|
+
if (typeof serverURL !== 'string') {
|
|
338
|
+
throw new Error(`client transport requires a server URL`)
|
|
339
|
+
}
|
|
340
|
+
transport.install(serverURL)
|
|
341
|
+
}
|
|
342
|
+
// recovery closures reach the instance through this ref so they always
|
|
343
|
+
// delete the CURRENT instance's own store (set right after construction;
|
|
344
|
+
// the handlers only fire post-mount).
|
|
345
|
+
const instanceRef: { current: ZeroInstance | null } = { current: null }
|
|
346
|
+
const recoveryDeps: ZeroRecoveryDeps = {
|
|
347
|
+
deleteLocalState: () => deleteZeroInstance(instanceRef.current),
|
|
348
|
+
zeroEvents,
|
|
349
|
+
beforeReload,
|
|
350
|
+
scheduleReload,
|
|
351
|
+
guardStorage,
|
|
352
|
+
benignLogPatterns: [
|
|
353
|
+
...(transport?.logClassifications?.benign ?? []),
|
|
354
|
+
...(benignLogPatterns ?? []),
|
|
355
|
+
],
|
|
356
|
+
onRecovery: () => mutationLifecycle.fence(),
|
|
357
|
+
}
|
|
358
|
+
const recovery = makeZeroRecovery(recoveryDeps)
|
|
359
|
+
const createdInstance = new ZeroClient<Schema, ZeroMutators>({
|
|
360
|
+
kvStore: 'mem',
|
|
361
|
+
...options,
|
|
362
|
+
schema,
|
|
363
|
+
// @ts-expect-error same erasure ZeroProvider needed
|
|
364
|
+
mutators: getClientMutators(),
|
|
365
|
+
// when the consumer brings no logSink, install ours: it preserves Zero's
|
|
366
|
+
// console output AND watches for the local-store-lost signature. a
|
|
367
|
+
// consumer with its own logSink owns log-based recovery (no double-fire
|
|
368
|
+
// with e.g. soot's origin-gated recovery).
|
|
369
|
+
logSink: options.logSink ?? composeRecoveryLogSink(recoveryDeps),
|
|
370
|
+
// consumer handlers win; otherwise on-zero's default self-healing
|
|
371
|
+
// recovery covers EVERY reason (drop local state + reload, guarded) —
|
|
372
|
+
// passing these to Zero disables its built-in reload, so any reason we
|
|
373
|
+
// left unhandled would fatal-blank the app forever.
|
|
374
|
+
onUpdateNeeded: options.onUpdateNeeded ?? recovery.onUpdateNeeded,
|
|
375
|
+
onClientStateNotFound:
|
|
376
|
+
options.onClientStateNotFound ?? recovery.onClientStateNotFound,
|
|
377
|
+
})
|
|
378
|
+
instanceRef.current = createdInstance
|
|
379
|
+
return createdInstance
|
|
380
|
+
}
|
|
381
|
+
|
|
294
382
|
let latestZeroInstance: ZeroInstance | null = null
|
|
295
383
|
const zeroReadyWaiters = new Set<(instance: ZeroInstance) => void>()
|
|
296
384
|
|
|
385
|
+
// publish the active instance through the stable facade and query runner.
|
|
386
|
+
// the provider calls this during render (before descendant effects do
|
|
387
|
+
// imperative work) and connectHeadless calls it directly — the `zero` proxy,
|
|
388
|
+
// run(), and waitForZero() resolve identically either way.
|
|
389
|
+
function publishZeroInstance(zeroInstance: ZeroInstance): boolean {
|
|
390
|
+
if (zeroInstance === latestZeroInstance) return false
|
|
391
|
+
latestZeroInstance = zeroInstance
|
|
392
|
+
mutationLifecycle.activate()
|
|
393
|
+
const runner: ZeroRunner = (query, options) => zeroInstance.run(query as any, options)
|
|
394
|
+
// the instance-keyed runner is what run() dispatches owned namespaces to;
|
|
395
|
+
// the global runner stays as the ambient fallback (inline zql)
|
|
396
|
+
instance.runner = runner
|
|
397
|
+
setRunner(runner)
|
|
398
|
+
const waiters = [...zeroReadyWaiters]
|
|
399
|
+
zeroReadyWaiters.clear()
|
|
400
|
+
for (const onReady of waiters) onReady(zeroInstance)
|
|
401
|
+
return true
|
|
402
|
+
}
|
|
403
|
+
|
|
297
404
|
function waitForZero({ signal }: WaitForZeroOptions = {}): Promise<ZeroInstance> {
|
|
298
405
|
if (latestZeroInstance) return Promise.resolve(latestZeroInstance)
|
|
299
406
|
if (signal?.aborted) {
|
|
@@ -733,19 +840,6 @@ export function createZeroClientInternal<
|
|
|
733
840
|
// (mutations read auth dynamically via getAuthData() to avoid stale closure race condition)
|
|
734
841
|
setAuthData(authData)
|
|
735
842
|
|
|
736
|
-
// mutators are stable — auth is read dynamically via getAuthData() at mutation
|
|
737
|
-
// time, so we don't need to recreate them (or the Zero instance) on auth change.
|
|
738
|
-
// setAuthData() above already ensures getAuthData() returns current auth.
|
|
739
|
-
const mutators = useMemo(() => {
|
|
740
|
-
return createMutators({
|
|
741
|
-
models,
|
|
742
|
-
environment: 'client',
|
|
743
|
-
authData: null,
|
|
744
|
-
can: permissionsHelpers.can,
|
|
745
|
-
})
|
|
746
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
747
|
-
}, [])
|
|
748
|
-
|
|
749
843
|
// host-scoped storage: composed here so embedding hosts isolate co-located
|
|
750
844
|
// apps without app code carrying host globals. static per page load — the
|
|
751
845
|
// host injects the scope before the app's module graph evaluates.
|
|
@@ -839,19 +933,6 @@ export function createZeroClientInternal<
|
|
|
839
933
|
'schema' | 'mutators'
|
|
840
934
|
>
|
|
841
935
|
|
|
842
|
-
// install before construction so the instance's first connect goes
|
|
843
|
-
// through HTTP. per-origin idempotence is strict: a second provider may
|
|
844
|
-
// reuse this transport only when every behavior option matches.
|
|
845
|
-
if (transport) {
|
|
846
|
-
// same precedence as zero's own getServer (cacheURL is the current
|
|
847
|
-
// option name; server is its deprecated alias)
|
|
848
|
-
const serverURL = options.cacheURL ?? options.server
|
|
849
|
-
if (typeof serverURL !== 'string') {
|
|
850
|
-
throw new Error(`client transport requires a server URL`)
|
|
851
|
-
}
|
|
852
|
-
transport.install(serverURL)
|
|
853
|
-
}
|
|
854
|
-
|
|
855
936
|
if (cached?.key !== instanceKey) {
|
|
856
937
|
if (cached) {
|
|
857
938
|
// the replacement's SetZeroInstance effect publishes one version
|
|
@@ -860,44 +941,17 @@ export function createZeroClientInternal<
|
|
|
860
941
|
mutationLifecycle.fence()
|
|
861
942
|
cached.instance.close()
|
|
862
943
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
benignLogPatterns: [
|
|
874
|
-
...(transport?.logClassifications?.benign ?? []),
|
|
875
|
-
...(benignLogPatterns ?? []),
|
|
876
|
-
],
|
|
877
|
-
onRecovery: () => mutationLifecycle.fence(),
|
|
944
|
+
cached = {
|
|
945
|
+
key: instanceKey,
|
|
946
|
+
instance: constructZeroInstance({
|
|
947
|
+
options,
|
|
948
|
+
transport,
|
|
949
|
+
beforeReload,
|
|
950
|
+
scheduleReload,
|
|
951
|
+
guardStorage,
|
|
952
|
+
benignLogPatterns,
|
|
953
|
+
}),
|
|
878
954
|
}
|
|
879
|
-
const recovery = makeZeroRecovery(recoveryDeps)
|
|
880
|
-
const createdInstance = new ZeroClient<Schema, ZeroMutators>({
|
|
881
|
-
kvStore: 'mem',
|
|
882
|
-
...options,
|
|
883
|
-
schema,
|
|
884
|
-
// @ts-expect-error same erasure ZeroProvider needed
|
|
885
|
-
mutators,
|
|
886
|
-
// when the consumer brings no logSink, install ours: it preserves
|
|
887
|
-
// Zero's console output AND watches for the local-store-lost signature.
|
|
888
|
-
// a consumer with its own logSink owns log-based recovery (no
|
|
889
|
-
// double-fire with e.g. soot's origin-gated recovery).
|
|
890
|
-
logSink: options.logSink ?? composeRecoveryLogSink(recoveryDeps),
|
|
891
|
-
// consumer handlers win; otherwise on-zero's default self-healing
|
|
892
|
-
// recovery covers EVERY reason (drop local state + reload, guarded) —
|
|
893
|
-
// passing these to Zero disables its built-in reload, so any reason we
|
|
894
|
-
// left unhandled would fatal-blank the app forever.
|
|
895
|
-
onUpdateNeeded: options.onUpdateNeeded ?? recovery.onUpdateNeeded,
|
|
896
|
-
onClientStateNotFound:
|
|
897
|
-
options.onClientStateNotFound ?? recovery.onClientStateNotFound,
|
|
898
|
-
})
|
|
899
|
-
instanceRef.current = createdInstance
|
|
900
|
-
cached = { key: instanceKey, instance: createdInstance }
|
|
901
955
|
cachedZero = cached
|
|
902
956
|
}
|
|
903
957
|
setInstance(cached.instance)
|
|
@@ -964,21 +1018,8 @@ export function createZeroClientInternal<
|
|
|
964
1018
|
const SetZeroInstance = () => {
|
|
965
1019
|
const zeroInstance = useZero<Schema, ZeroMutators>()
|
|
966
1020
|
|
|
967
|
-
// publish
|
|
968
|
-
|
|
969
|
-
if (zeroInstance !== latestZeroInstance) {
|
|
970
|
-
latestZeroInstance = zeroInstance
|
|
971
|
-
mutationLifecycle.activate()
|
|
972
|
-
const runner: ZeroRunner = (query, options) =>
|
|
973
|
-
zeroInstance.run(query as any, options)
|
|
974
|
-
// the instance-keyed runner is what run() dispatches owned namespaces
|
|
975
|
-
// to; the global runner stays as the ambient fallback (inline zql)
|
|
976
|
-
instance.runner = runner
|
|
977
|
-
setRunner(runner)
|
|
978
|
-
const waiters = [...zeroReadyWaiters]
|
|
979
|
-
zeroReadyWaiters.clear()
|
|
980
|
-
for (const onReady of waiters) onReady(zeroInstance)
|
|
981
|
-
}
|
|
1021
|
+
// publish before descendant effects perform imperative work.
|
|
1022
|
+
publishZeroInstance(zeroInstance)
|
|
982
1023
|
|
|
983
1024
|
useEffect(() => {
|
|
984
1025
|
zeroInstanceVersion?.emit(zeroInstanceVersion.value + 1)
|
|
@@ -987,13 +1028,203 @@ export function createZeroClientInternal<
|
|
|
987
1028
|
return null
|
|
988
1029
|
}
|
|
989
1030
|
|
|
1031
|
+
// connect WITHOUT react. same construction and same publication the provider
|
|
1032
|
+
// uses, so `zero`, run(), getQuery(), and waitForZero() all resolve exactly as
|
|
1033
|
+
// they do in a mounted app — which is what lets code written against the
|
|
1034
|
+
// module-global facade run unchanged in a worker, a durable object, or a
|
|
1035
|
+
// script. the caller owns the lifetime and must close().
|
|
1036
|
+
//
|
|
1037
|
+
// there is no react tree here, so nothing owns rotation: an identity change
|
|
1038
|
+
// means close this instance and connect a new one. the host is expected to be
|
|
1039
|
+
// scoped to a single identity for its lifetime (one project, one user).
|
|
1040
|
+
function connectHeadless(
|
|
1041
|
+
props: Omit<ZeroOptions<Schema, ZeroMutators>, 'schema' | 'mutators'> & {
|
|
1042
|
+
authData?: AuthData | null
|
|
1043
|
+
transport?: ZeroProviderTransport
|
|
1044
|
+
beforeReload?: () => Promise<void>
|
|
1045
|
+
scheduleReload?: (ctx: ScheduleReloadContext) => void
|
|
1046
|
+
guardStorage?: RecoveryGuardStorage
|
|
1047
|
+
benignLogPatterns?: readonly ZeroLogPattern[]
|
|
1048
|
+
// mint a fresh token. REQUIRED for any host that outlives its token: zero
|
|
1049
|
+
// parks in needs-auth and will not resume until the auth string changes,
|
|
1050
|
+
// so without this a long-lived headless host answers 401 forever.
|
|
1051
|
+
refreshAuth?: () => Promise<string | undefined>
|
|
1052
|
+
}
|
|
1053
|
+
): { zero: ZeroInstance; close: () => Promise<void> } {
|
|
1054
|
+
const {
|
|
1055
|
+
authData,
|
|
1056
|
+
transport,
|
|
1057
|
+
beforeReload,
|
|
1058
|
+
scheduleReload,
|
|
1059
|
+
guardStorage,
|
|
1060
|
+
benignLogPatterns,
|
|
1061
|
+
refreshAuth,
|
|
1062
|
+
...options
|
|
1063
|
+
} = props
|
|
1064
|
+
// mutations read auth dynamically through getAuthData(), so this has to be
|
|
1065
|
+
// set before the first mutation exactly as the provider sets it in render.
|
|
1066
|
+
setAuthData((authData ?? null) as AuthData)
|
|
1067
|
+
const zeroInstance = constructZeroInstance({
|
|
1068
|
+
options,
|
|
1069
|
+
transport,
|
|
1070
|
+
beforeReload,
|
|
1071
|
+
scheduleReload,
|
|
1072
|
+
guardStorage,
|
|
1073
|
+
benignLogPatterns,
|
|
1074
|
+
})
|
|
1075
|
+
publishZeroInstance(zeroInstance)
|
|
1076
|
+
zeroInstanceVersion?.emit(zeroInstanceVersion.value + 1)
|
|
1077
|
+
const unwatch = watchZeroConnection({ zeroInstance, refreshAuth })
|
|
1078
|
+
return {
|
|
1079
|
+
zero: zeroInstance,
|
|
1080
|
+
close: async () => {
|
|
1081
|
+
unwatch()
|
|
1082
|
+
clearZeroInstanceReferences(zeroInstance)
|
|
1083
|
+
mutationLifecycle.fence()
|
|
1084
|
+
await zeroInstance.close()
|
|
1085
|
+
},
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// watch a zero instance's connection and own the generic recovery: stale-poke
|
|
1090
|
+
// and transport reconnects, needs-auth token refresh, reconnect-status events,
|
|
1091
|
+
// and optional dataset mirroring. plain subscription, no react — a headless
|
|
1092
|
+
// host needs exactly this and previously got none of it, so its token expiry
|
|
1093
|
+
// ended as a permanent 401 with no way back.
|
|
1094
|
+
function watchZeroConnection(args: {
|
|
1095
|
+
zeroInstance: ZeroInstance
|
|
1096
|
+
refreshAuth?: () => Promise<string | undefined>
|
|
1097
|
+
exposeDataset?: boolean
|
|
1098
|
+
datasetCacheUrl?: string
|
|
1099
|
+
}): () => void {
|
|
1100
|
+
const { zeroInstance, refreshAuth, exposeDataset, datasetCacheUrl } = args
|
|
1101
|
+
let prevState = zeroInstance.connection.state.current.name
|
|
1102
|
+
let hasConnected = false
|
|
1103
|
+
const currentReconnect =
|
|
1104
|
+
zeroEvents.value?.type === 'reconnect' && zeroEvents.value.status !== 'connected'
|
|
1105
|
+
? zeroEvents.value
|
|
1106
|
+
: null
|
|
1107
|
+
let reconnect: { reasonKey: ZeroReconnectReasonKey; reason: string } | null =
|
|
1108
|
+
currentReconnect
|
|
1109
|
+
? { reasonKey: currentReconnect.reasonKey, reason: currentReconnect.reason }
|
|
1110
|
+
: null
|
|
1111
|
+
// one reconnect per distinct recoverable error / one refresh per needs-auth
|
|
1112
|
+
// transition, so a stuck state doesn't retry-storm.
|
|
1113
|
+
let recoverableError: string | null = null
|
|
1114
|
+
let needsAuth = false
|
|
1115
|
+
|
|
1116
|
+
const handle = () => {
|
|
1117
|
+
const state = zeroInstance.connection.state.current
|
|
1118
|
+
const name = state.name
|
|
1119
|
+
const reason =
|
|
1120
|
+
'reason' in state && typeof state.reason === 'string' ? state.reason : ''
|
|
1121
|
+
|
|
1122
|
+
// mirror connection state onto the body dataset for e2e/diagnostics
|
|
1123
|
+
// (enabled on one instance so instances don't clobber each other).
|
|
1124
|
+
if (exposeDataset && typeof document !== 'undefined' && document.body) {
|
|
1125
|
+
document.body.dataset.zeroState = name
|
|
1126
|
+
if (datasetCacheUrl) document.body.dataset.zeroCacheUrl = datasetCacheUrl
|
|
1127
|
+
if (reason) document.body.dataset.zeroReason = reason.slice(0, 200)
|
|
1128
|
+
else delete document.body.dataset.zeroReason
|
|
1129
|
+
if (name === 'connected') document.body.dataset.zeroConnected = 'true'
|
|
1130
|
+
else delete document.body.dataset.zeroConnected
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
if (name === 'connected') {
|
|
1134
|
+
hasConnected = true
|
|
1135
|
+
recoverableError = null
|
|
1136
|
+
if (reconnect) {
|
|
1137
|
+
reconnect = null
|
|
1138
|
+
emitReconnectStatus({ type: 'reconnect', status: 'connected' })
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
const reconnectReasonKey: ZeroReconnectReasonKey | undefined = reason.includes(
|
|
1143
|
+
'ServerOverloaded'
|
|
1144
|
+
)
|
|
1145
|
+
? 'server-overloaded'
|
|
1146
|
+
: reason.includes('Failed to fetch') ||
|
|
1147
|
+
reason.includes('fetch failed') ||
|
|
1148
|
+
reason.includes('NetworkError when attempting to fetch resource') ||
|
|
1149
|
+
reason.includes('Network request failed') ||
|
|
1150
|
+
reason.includes('Load failed')
|
|
1151
|
+
? 'transport'
|
|
1152
|
+
: undefined
|
|
1153
|
+
|
|
1154
|
+
// stale-poke and paused transport errors both resume the existing client.
|
|
1155
|
+
// ServerOverloaded remains in Zero's own retry/backoff loop.
|
|
1156
|
+
if (
|
|
1157
|
+
name === 'error' &&
|
|
1158
|
+
(isRecoverableZeroStalePokeMessage(reason) || reconnectReasonKey)
|
|
1159
|
+
) {
|
|
1160
|
+
if (recoverableError !== reason) {
|
|
1161
|
+
recoverableError = reason
|
|
1162
|
+
reconnect = { reasonKey: reconnectReasonKey ?? 'transport', reason }
|
|
1163
|
+
emitReconnectStatus({ type: 'reconnect', status: 'trying', ...reconnect })
|
|
1164
|
+
void Promise.resolve(zeroInstance.connection?.connect?.()).catch(() => {})
|
|
1165
|
+
}
|
|
1166
|
+
return
|
|
1167
|
+
}
|
|
1168
|
+
if (name !== 'error') recoverableError = null
|
|
1169
|
+
|
|
1170
|
+
if (name === 'connecting' && (reconnect || hasConnected || Boolean(reason))) {
|
|
1171
|
+
reconnect = {
|
|
1172
|
+
reasonKey: reconnectReasonKey ?? reconnect?.reasonKey ?? 'transport',
|
|
1173
|
+
reason: reason || reconnect?.reason || 'connection interrupted',
|
|
1174
|
+
}
|
|
1175
|
+
emitReconnectStatus({
|
|
1176
|
+
type: 'reconnect',
|
|
1177
|
+
status: reason ? 'waiting' : 'trying',
|
|
1178
|
+
...reconnect,
|
|
1179
|
+
})
|
|
1180
|
+
} else if (name === 'disconnected' && (reconnect || hasConnected)) {
|
|
1181
|
+
reconnect = {
|
|
1182
|
+
reasonKey: reconnect?.reasonKey ?? 'transport',
|
|
1183
|
+
reason: reason || reconnect?.reason || 'connection interrupted',
|
|
1184
|
+
}
|
|
1185
|
+
emitReconnectStatus({ type: 'reconnect', status: 'waiting', ...reconnect })
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// needs-auth: the token expired and zero won't auto-resume unless the
|
|
1189
|
+
// auth string changes. refresh it and reconnect in place, once.
|
|
1190
|
+
if (name === 'needs-auth') {
|
|
1191
|
+
if (refreshAuth && !needsAuth) {
|
|
1192
|
+
needsAuth = true
|
|
1193
|
+
void refreshAuth()
|
|
1194
|
+
.then((token) => {
|
|
1195
|
+
if (token) zeroInstance.connection?.connect?.({ auth: token })
|
|
1196
|
+
})
|
|
1197
|
+
.catch(() => {})
|
|
1198
|
+
}
|
|
1199
|
+
} else {
|
|
1200
|
+
needsAuth = false
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (name !== prevState) {
|
|
1204
|
+
prevState = name
|
|
1205
|
+
if (name === 'error' || name === 'needs-auth') {
|
|
1206
|
+
zeroEvents.emit({
|
|
1207
|
+
type: 'error',
|
|
1208
|
+
reasonKey:
|
|
1209
|
+
name === 'needs-auth' ? 'connection-needs-auth' : 'connection-error',
|
|
1210
|
+
message: reason || name,
|
|
1211
|
+
})
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
const unsubscribe = zeroInstance.connection.state.subscribe(handle)
|
|
1217
|
+
handle()
|
|
1218
|
+
return unsubscribe
|
|
1219
|
+
}
|
|
1220
|
+
|
|
990
1221
|
// monitors connection state and emits events (replaces onError callback removed
|
|
991
1222
|
// in 0.25). also owns the generic-Zero connection recovery that used to live in
|
|
992
1223
|
// each consumer: stale-poke reconnect, needs-auth token refresh, and optional
|
|
993
1224
|
// e2e dataset bookkeeping.
|
|
994
1225
|
const ConnectionMonitor = memo(
|
|
995
1226
|
({
|
|
996
|
-
zeroEvents,
|
|
1227
|
+
zeroEvents: _zeroEvents,
|
|
997
1228
|
refreshAuth,
|
|
998
1229
|
exposeDataset,
|
|
999
1230
|
datasetCacheUrl,
|
|
@@ -1004,146 +1235,19 @@ export function createZeroClientInternal<
|
|
|
1004
1235
|
datasetCacheUrl?: string
|
|
1005
1236
|
}) => {
|
|
1006
1237
|
const zeroInstance = useZero<Schema, ZeroMutators>()
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
? {
|
|
1020
|
-
reasonKey: currentReconnect.reasonKey,
|
|
1021
|
-
reason: currentReconnect.reason,
|
|
1022
|
-
}
|
|
1023
|
-
: null
|
|
1238
|
+
// the recovery logic itself is not react's — it is a subscription to the
|
|
1239
|
+
// instance's connection state, shared with connectHeadless so a non-react
|
|
1240
|
+
// host recovers identically instead of going dark on token expiry.
|
|
1241
|
+
useEffect(
|
|
1242
|
+
() =>
|
|
1243
|
+
watchZeroConnection({
|
|
1244
|
+
zeroInstance,
|
|
1245
|
+
refreshAuth,
|
|
1246
|
+
exposeDataset,
|
|
1247
|
+
datasetCacheUrl,
|
|
1248
|
+
}),
|
|
1249
|
+
[zeroInstance, refreshAuth, exposeDataset, datasetCacheUrl]
|
|
1024
1250
|
)
|
|
1025
|
-
// one reconnect per distinct recoverable error / one refresh per
|
|
1026
|
-
// needs-auth transition, so a stuck state doesn't retry-storm.
|
|
1027
|
-
const recoverableErrorRef = useRef<string | null>(null)
|
|
1028
|
-
const needsAuthRef = useRef(false)
|
|
1029
|
-
|
|
1030
|
-
useEffect(() => {
|
|
1031
|
-
const name = state.name
|
|
1032
|
-
const reason =
|
|
1033
|
-
'reason' in state && typeof state.reason === 'string' ? state.reason : ''
|
|
1034
|
-
|
|
1035
|
-
// mirror connection state onto the body dataset for e2e/diagnostics
|
|
1036
|
-
// (enabled on one instance so instances don't clobber each other).
|
|
1037
|
-
if (exposeDataset && typeof document !== 'undefined' && document.body) {
|
|
1038
|
-
document.body.dataset.zeroState = name
|
|
1039
|
-
if (datasetCacheUrl) document.body.dataset.zeroCacheUrl = datasetCacheUrl
|
|
1040
|
-
if (reason) document.body.dataset.zeroReason = reason.slice(0, 200)
|
|
1041
|
-
else delete document.body.dataset.zeroReason
|
|
1042
|
-
if (name === 'connected') document.body.dataset.zeroConnected = 'true'
|
|
1043
|
-
else delete document.body.dataset.zeroConnected
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
if (name === 'connected') {
|
|
1047
|
-
hasConnectedRef.current = true
|
|
1048
|
-
recoverableErrorRef.current = null
|
|
1049
|
-
if (reconnectRef.current) {
|
|
1050
|
-
reconnectRef.current = null
|
|
1051
|
-
emitReconnectStatus({ type: 'reconnect', status: 'connected' })
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
const reconnectReasonKey: ZeroReconnectReasonKey | undefined = reason.includes(
|
|
1056
|
-
'ServerOverloaded'
|
|
1057
|
-
)
|
|
1058
|
-
? 'server-overloaded'
|
|
1059
|
-
: reason.includes('Failed to fetch') ||
|
|
1060
|
-
reason.includes('fetch failed') ||
|
|
1061
|
-
reason.includes('NetworkError when attempting to fetch resource') ||
|
|
1062
|
-
reason.includes('Network request failed') ||
|
|
1063
|
-
reason.includes('Load failed')
|
|
1064
|
-
? 'transport'
|
|
1065
|
-
: undefined
|
|
1066
|
-
|
|
1067
|
-
// stale-poke and paused transport errors both resume the existing
|
|
1068
|
-
// client. ServerOverloaded remains in Zero's own retry/backoff loop.
|
|
1069
|
-
if (
|
|
1070
|
-
name === 'error' &&
|
|
1071
|
-
(isRecoverableZeroStalePokeMessage(reason) || reconnectReasonKey)
|
|
1072
|
-
) {
|
|
1073
|
-
if (recoverableErrorRef.current !== reason) {
|
|
1074
|
-
recoverableErrorRef.current = reason
|
|
1075
|
-
reconnectRef.current = {
|
|
1076
|
-
reasonKey: reconnectReasonKey ?? 'transport',
|
|
1077
|
-
reason,
|
|
1078
|
-
}
|
|
1079
|
-
emitReconnectStatus({
|
|
1080
|
-
type: 'reconnect',
|
|
1081
|
-
status: 'trying',
|
|
1082
|
-
...reconnectRef.current,
|
|
1083
|
-
})
|
|
1084
|
-
void Promise.resolve(zeroInstance.connection?.connect?.()).catch(() => {})
|
|
1085
|
-
}
|
|
1086
|
-
return
|
|
1087
|
-
}
|
|
1088
|
-
if (name !== 'error') recoverableErrorRef.current = null
|
|
1089
|
-
|
|
1090
|
-
if (
|
|
1091
|
-
name === 'connecting' &&
|
|
1092
|
-
(reconnectRef.current || hasConnectedRef.current || Boolean(reason))
|
|
1093
|
-
) {
|
|
1094
|
-
reconnectRef.current = {
|
|
1095
|
-
reasonKey:
|
|
1096
|
-
reconnectReasonKey ?? reconnectRef.current?.reasonKey ?? 'transport',
|
|
1097
|
-
reason: reason || reconnectRef.current?.reason || 'connection interrupted',
|
|
1098
|
-
}
|
|
1099
|
-
emitReconnectStatus({
|
|
1100
|
-
type: 'reconnect',
|
|
1101
|
-
status: reason ? 'waiting' : 'trying',
|
|
1102
|
-
...reconnectRef.current,
|
|
1103
|
-
})
|
|
1104
|
-
} else if (
|
|
1105
|
-
name === 'disconnected' &&
|
|
1106
|
-
(reconnectRef.current || hasConnectedRef.current)
|
|
1107
|
-
) {
|
|
1108
|
-
reconnectRef.current = {
|
|
1109
|
-
reasonKey: reconnectRef.current?.reasonKey ?? 'transport',
|
|
1110
|
-
reason: reason || reconnectRef.current?.reason || 'connection interrupted',
|
|
1111
|
-
}
|
|
1112
|
-
emitReconnectStatus({
|
|
1113
|
-
type: 'reconnect',
|
|
1114
|
-
status: 'waiting',
|
|
1115
|
-
...reconnectRef.current,
|
|
1116
|
-
})
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
// needs-auth: the token expired and zero won't auto-resume unless the
|
|
1120
|
-
// auth string changes. refresh it and reconnect in place, once.
|
|
1121
|
-
if (name === 'needs-auth') {
|
|
1122
|
-
if (refreshAuth && !needsAuthRef.current) {
|
|
1123
|
-
needsAuthRef.current = true
|
|
1124
|
-
void refreshAuth()
|
|
1125
|
-
.then((token) => {
|
|
1126
|
-
if (token) zeroInstance.connection?.connect?.({ auth: token })
|
|
1127
|
-
})
|
|
1128
|
-
.catch(() => {})
|
|
1129
|
-
}
|
|
1130
|
-
} else {
|
|
1131
|
-
needsAuthRef.current = false
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
if (name !== prevState.current) {
|
|
1135
|
-
prevState.current = name
|
|
1136
|
-
if (name === 'error' || name === 'needs-auth') {
|
|
1137
|
-
zeroEvents.emit({
|
|
1138
|
-
type: 'error',
|
|
1139
|
-
reasonKey:
|
|
1140
|
-
name === 'needs-auth' ? 'connection-needs-auth' : 'connection-error',
|
|
1141
|
-
message: reason || name,
|
|
1142
|
-
})
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
}, [state, zeroEvents, zeroInstance, refreshAuth, exposeDataset, datasetCacheUrl])
|
|
1146
|
-
|
|
1147
1251
|
return null
|
|
1148
1252
|
}
|
|
1149
1253
|
)
|
|
@@ -1202,6 +1306,7 @@ export function createZeroClientInternal<
|
|
|
1202
1306
|
zeroEvents,
|
|
1203
1307
|
reloadPage,
|
|
1204
1308
|
ProvideZero,
|
|
1309
|
+
connectHeadless,
|
|
1205
1310
|
ControlQueries,
|
|
1206
1311
|
useQuery,
|
|
1207
1312
|
useQueryDirect,
|
package/src/zeroRunner.ts
CHANGED
|
@@ -37,7 +37,7 @@ export function getAmbientRunner(instance?: { runner: ZeroRunner | null }): Zero
|
|
|
37
37
|
|
|
38
38
|
if (!runner) {
|
|
39
39
|
throw new Error(
|
|
40
|
-
'Zero runner not initialized. Ensure ProvideZero is mounted or server bindings are active.'
|
|
40
|
+
'Zero runner not initialized. Ensure ProvideZero is mounted, connectHeadless() was called, or server bindings are active.'
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -50,6 +50,18 @@ export declare function createZeroClient<Schema extends ZeroSchema, Models exten
|
|
|
50
50
|
refreshAuth?: (() => Promise<string | undefined>) | undefined;
|
|
51
51
|
connectionDataset?: boolean;
|
|
52
52
|
}) => import("react").JSX.Element;
|
|
53
|
+
connectHeadless: (props: Omit<ZeroOptions<Schema, GetZeroMutators<Models>>, "schema" | "mutators"> & {
|
|
54
|
+
authData?: {} | null | undefined;
|
|
55
|
+
transport?: ZeroProviderTransport;
|
|
56
|
+
beforeReload?: (() => Promise<void>) | undefined;
|
|
57
|
+
scheduleReload?: ((ctx: ScheduleReloadContext) => void) | undefined;
|
|
58
|
+
guardStorage?: RecoveryGuardStorage;
|
|
59
|
+
benignLogPatterns?: readonly ZeroLogPattern[];
|
|
60
|
+
refreshAuth?: (() => Promise<string | undefined>) | undefined;
|
|
61
|
+
}) => {
|
|
62
|
+
zero: ZeroClient<Schema, GetZeroMutators<Models>, unknown>;
|
|
63
|
+
close: () => Promise<void>;
|
|
64
|
+
};
|
|
53
65
|
ControlQueries: ({ children, action, whenDisabled, }: {
|
|
54
66
|
children: ReactNode;
|
|
55
67
|
action?: "enable" | "disable";
|
|
@@ -100,6 +112,18 @@ export declare function createZeroClientInternal<Schema extends ZeroSchema, Mode
|
|
|
100
112
|
refreshAuth?: () => Promise<string | undefined>;
|
|
101
113
|
connectionDataset?: boolean;
|
|
102
114
|
}) => import("react").JSX.Element;
|
|
115
|
+
connectHeadless: (props: Omit<ZeroOptions<Schema, GetZeroMutators<Models>>, "schema" | "mutators"> & {
|
|
116
|
+
authData?: AuthData | null;
|
|
117
|
+
transport?: ZeroProviderTransport;
|
|
118
|
+
beforeReload?: () => Promise<void>;
|
|
119
|
+
scheduleReload?: (ctx: ScheduleReloadContext) => void;
|
|
120
|
+
guardStorage?: RecoveryGuardStorage;
|
|
121
|
+
benignLogPatterns?: readonly ZeroLogPattern[];
|
|
122
|
+
refreshAuth?: () => Promise<string | undefined>;
|
|
123
|
+
}) => {
|
|
124
|
+
zero: ZeroClient<Schema, GetZeroMutators<Models>, unknown>;
|
|
125
|
+
close: () => Promise<void>;
|
|
126
|
+
};
|
|
103
127
|
ControlQueries: ({ children, action, whenDisabled, }: {
|
|
104
128
|
children: ReactNode;
|
|
105
129
|
action?: "enable" | "disable";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createZeroClient.d.ts","sourceRoot":"","sources":["../src/createZeroClient.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,IAAI,IAAI,UAAU,EACnB,MAAM,gBAAgB,CAAA;AAOvB,OAAO,EASL,KAAK,OAAO,EACZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAGd,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAK/D,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EAEpB,MAAM,6BAA6B,CAAA;AAIpC,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAOhE,OAAO,KAAK,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EAEf,iBAAiB,EAElB,MAAM,SAAS,CAAA;AAChB,OAAO,KAAK,EACV,gBAAgB,EAChB,KAAK,EACL,GAAG,EAEH,WAAW,EACX,MAAM,IAAI,UAAU,EACrB,MAAM,gBAAgB,CAAA;AAEvB,KAAK,cAAc,GAAG;IAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AAEvE,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;AAMpF,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,iBAAiB,GAAG,kBAAkB,CAAA;AAEtF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IACnC,kBAAkB,CAAC,EAAE;QACnB,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;KACnC,CAAA;CACF,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,uBAAuB,CACjC,MAAM,SAAS,UAAU,EACzB,MAAM,SAAS,aAAa,IAC1B;IACF,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,cAAc,CAAA;IAC9B,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;IAGvC,iCAAiC,CAAC,EAAE,MAAM,CAAA;IAI1C,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,kBAAkB,CAAC,MAAM,SAAS,UAAU,IAAI,CAAC,KAAK,EAAE;IAClE,eAAe,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC1C,aAAa,EAAE,gBAAgB,CAAA;IAC/B,OAAO,EAAE,MAAM,GAAG,CAAA;IAClB,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;CAC7B,KAAK,YAAY,CAAC,MAAM,CAAC,CAAA;AA+B1B,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,UAAU,EAAE,MAAM,SAAS,aAAa,EACtF,OAAO,EAAE,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC;;;
|
|
1
|
+
{"version":3,"file":"createZeroClient.d.ts","sourceRoot":"","sources":["../src/createZeroClient.tsx"],"names":[],"mappings":"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"}
|