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.
Files changed (35) hide show
  1. package/dist/cjs/createZeroClient.cjs +176 -100
  2. package/dist/cjs/createZeroClient.connection.test.cjs +32 -0
  3. package/dist/cjs/createZeroClient.connection.test.native.js +34 -0
  4. package/dist/cjs/createZeroClient.connection.test.native.js.map +1 -1
  5. package/dist/cjs/createZeroClient.headless.test.cjs +33 -0
  6. package/dist/cjs/createZeroClient.headless.test.native.js +38 -0
  7. package/dist/cjs/createZeroClient.headless.test.native.js.map +1 -0
  8. package/dist/cjs/createZeroClient.native.js +207 -132
  9. package/dist/cjs/createZeroClient.native.js.map +1 -1
  10. package/dist/cjs/zeroRunner.cjs +1 -1
  11. package/dist/cjs/zeroRunner.native.js +1 -1
  12. package/dist/esm/createZeroClient.connection.test.mjs +32 -0
  13. package/dist/esm/createZeroClient.connection.test.mjs.map +1 -1
  14. package/dist/esm/createZeroClient.connection.test.native.js +34 -0
  15. package/dist/esm/createZeroClient.connection.test.native.js.map +1 -1
  16. package/dist/esm/createZeroClient.headless.test.mjs +34 -0
  17. package/dist/esm/createZeroClient.headless.test.mjs.map +1 -0
  18. package/dist/esm/createZeroClient.headless.test.native.js +36 -0
  19. package/dist/esm/createZeroClient.headless.test.native.js.map +1 -0
  20. package/dist/esm/createZeroClient.mjs +178 -102
  21. package/dist/esm/createZeroClient.mjs.map +1 -1
  22. package/dist/esm/createZeroClient.native.js +209 -134
  23. package/dist/esm/createZeroClient.native.js.map +1 -1
  24. package/dist/esm/zeroRunner.mjs +1 -1
  25. package/dist/esm/zeroRunner.mjs.map +1 -1
  26. package/dist/esm/zeroRunner.native.js +1 -1
  27. package/package.json +1 -1
  28. package/src/createZeroClient.connection.test.tsx +38 -0
  29. package/src/createZeroClient.headless.test.tsx +47 -0
  30. package/src/createZeroClient.tsx +323 -218
  31. package/src/zeroRunner.ts +1 -1
  32. package/types/createZeroClient.d.ts +24 -0
  33. package/types/createZeroClient.d.ts.map +1 -1
  34. package/types/createZeroClient.headless.test.d.ts +2 -0
  35. package/types/createZeroClient.headless.test.d.ts.map +1 -0
@@ -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
- // recovery closures reach the instance through this ref so they always
864
- // delete the CURRENT instance's own store (set right after construction;
865
- // the handlers only fire post-mount).
866
- const instanceRef: { current: ZeroInstance | null } = { current: null }
867
- const recoveryDeps: ZeroRecoveryDeps = {
868
- deleteLocalState: () => deleteZeroInstance(instanceRef.current),
869
- zeroEvents,
870
- beforeReload,
871
- scheduleReload,
872
- guardStorage,
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 the active external instance through the stable facade and query
968
- // runner before descendant effects perform imperative work.
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
- const state = useConnectionState()
1008
- const prevState = useRef(state.name)
1009
- const hasConnectedRef = useRef(false)
1010
- const currentReconnect =
1011
- zeroEvents.value?.type === 'reconnect' && zeroEvents.value.status !== 'connected'
1012
- ? zeroEvents.value
1013
- : null
1014
- const reconnectRef = useRef<{
1015
- reasonKey: ZeroReconnectReasonKey
1016
- reason: string
1017
- } | null>(
1018
- currentReconnect
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":"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"}
1
+ {"version":3,"file":"createZeroClient.d.ts","sourceRoot":"","sources":["../src/createZeroClient.tsx"],"names":[],"mappings":"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"}