on-zero 0.7.10 → 0.8.0
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/{readme.md → README.md} +172 -231
- package/dist/cjs/cli.cjs +5 -2
- package/dist/cjs/cli.native.js +5 -2
- package/dist/cjs/cli.native.js.map +1 -1
- package/dist/cjs/combineZeroClients.cjs +2 -2
- package/dist/cjs/combineZeroClients.native.js +6 -6
- package/dist/cjs/combineZeroClients.native.js.map +1 -1
- package/dist/cjs/config.cjs +30 -0
- package/dist/cjs/config.native.js +33 -0
- package/dist/cjs/config.native.js.map +1 -0
- package/dist/cjs/createPermissions.cjs +2 -2
- package/dist/cjs/createPermissions.native.js +17 -27
- package/dist/cjs/createPermissions.native.js.map +1 -1
- package/dist/cjs/createUseQuery.cjs +3 -3
- package/dist/cjs/createUseQuery.native.js +4 -4
- package/dist/cjs/createUseQuery.native.js.map +1 -1
- package/dist/cjs/createUseQueryDirect.cjs +4 -3
- package/dist/cjs/createUseQueryDirect.native.js +14 -18
- package/dist/cjs/createUseQueryDirect.native.js.map +1 -1
- package/dist/cjs/createZeroClient.cjs +29 -9
- package/dist/cjs/createZeroClient.connection.test.cjs +11 -0
- package/dist/cjs/createZeroClient.connection.test.native.js +14 -7
- package/dist/cjs/createZeroClient.connection.test.native.js.map +1 -1
- package/dist/cjs/createZeroClient.native.js +58 -32
- package/dist/cjs/createZeroClient.native.js.map +1 -1
- package/dist/cjs/createZeroClient.recovery.test.cjs +90 -3
- package/dist/cjs/createZeroClient.recovery.test.native.js +109 -19
- package/dist/cjs/createZeroClient.recovery.test.native.js.map +1 -1
- package/dist/cjs/generate-helpers.cjs +70 -17
- package/dist/cjs/generate-helpers.native.js +110 -23
- package/dist/cjs/generate-helpers.native.js.map +1 -1
- package/dist/cjs/generate-layout.cjs +586 -0
- package/dist/cjs/generate-layout.native.js +1275 -0
- package/dist/cjs/generate-layout.native.js.map +1 -0
- package/dist/cjs/generate-lite.cjs +295 -27
- package/dist/cjs/generate-lite.native.js +854 -102
- package/dist/cjs/generate-lite.native.js.map +1 -1
- package/dist/cjs/generate-lite.test.cjs +433 -41
- package/dist/cjs/generate-lite.test.native.js +446 -41
- package/dist/cjs/generate-lite.test.native.js.map +1 -1
- package/dist/cjs/generate.cjs +172 -94
- package/dist/cjs/generate.native.js +324 -148
- package/dist/cjs/generate.native.js.map +1 -1
- package/dist/cjs/generate.test.cjs +362 -45
- package/dist/cjs/generate.test.native.js +374 -45
- package/dist/cjs/generate.test.native.js.map +1 -1
- package/dist/cjs/helpers/NullToOptional.cjs +18 -0
- package/dist/cjs/helpers/NullToOptional.native.js +21 -0
- package/dist/cjs/helpers/NullToOptional.native.js.map +1 -0
- package/dist/cjs/helpers/asyncContext.cjs +128 -0
- package/dist/cjs/helpers/asyncContext.native.js +87 -0
- package/dist/cjs/helpers/asyncContext.native.js.map +1 -0
- package/dist/cjs/helpers/batchQuery.cjs +2 -2
- package/dist/cjs/helpers/batchQuery.native.js +2 -2
- package/dist/cjs/helpers/batchQuery.native.js.map +1 -1
- package/dist/cjs/helpers/createMutators.cjs +7 -4
- package/dist/cjs/helpers/createMutators.native.js +12 -9
- package/dist/cjs/helpers/createMutators.native.js.map +1 -1
- package/dist/cjs/helpers/createMutators.test.native.js +2 -2
- package/dist/cjs/helpers/createMutators.test.native.js.map +1 -1
- package/dist/cjs/helpers/ellipsis.cjs +35 -0
- package/dist/cjs/helpers/ellipsis.native.js +38 -0
- package/dist/cjs/helpers/ellipsis.native.js.map +1 -0
- package/dist/cjs/helpers/emitter.cjs +99 -0
- package/dist/cjs/helpers/emitter.native.js +125 -0
- package/dist/cjs/helpers/emitter.native.js.map +1 -0
- package/dist/cjs/helpers/ensure.cjs +33 -0
- package/dist/cjs/helpers/ensure.native.js +37 -0
- package/dist/cjs/helpers/ensure.native.js.map +1 -0
- package/dist/cjs/helpers/ensureLoggedIn.cjs +2 -2
- package/dist/cjs/helpers/ensureLoggedIn.native.js +3 -3
- package/dist/cjs/helpers/ensureLoggedIn.native.js.map +1 -1
- package/dist/cjs/helpers/errors.cjs +40 -0
- package/dist/cjs/helpers/errors.native.js +152 -0
- package/dist/cjs/helpers/errors.native.js.map +1 -0
- package/dist/cjs/helpers/getCurrentComponentStack.cjs +71 -0
- package/dist/cjs/helpers/getCurrentComponentStack.native.js +93 -0
- package/dist/cjs/helpers/getCurrentComponentStack.native.js.map +1 -0
- package/dist/cjs/helpers/globalValue.cjs +39 -0
- package/dist/cjs/helpers/globalValue.native.js +42 -0
- package/dist/cjs/helpers/globalValue.native.js.map +1 -0
- package/dist/cjs/helpers/mapObject.cjs +36 -0
- package/dist/cjs/helpers/mapObject.native.js +39 -0
- package/dist/cjs/helpers/mapObject.native.js.map +1 -0
- package/dist/cjs/helpers/mutationLifecycle.cjs +251 -0
- package/dist/cjs/helpers/mutationLifecycle.native.js +392 -0
- package/dist/cjs/helpers/mutationLifecycle.native.js.map +1 -0
- package/dist/cjs/helpers/mutationLifecycle.test.cjs +174 -0
- package/dist/cjs/helpers/mutationLifecycle.test.native.js +182 -0
- package/dist/cjs/helpers/mutationLifecycle.test.native.js.map +1 -0
- package/dist/cjs/helpers/mutatorContext.cjs +3 -3
- package/dist/cjs/helpers/mutatorContext.native.js +5 -5
- package/dist/cjs/helpers/mutatorContext.native.js.map +1 -1
- package/dist/cjs/helpers/platform.cjs +31 -0
- package/dist/cjs/helpers/platform.native.js +33 -0
- package/dist/cjs/helpers/platform.native.js.map +1 -0
- package/dist/cjs/helpers/prettyFormatZeroQuery.cjs +2 -2
- package/dist/cjs/helpers/prettyFormatZeroQuery.native.js +7 -7
- package/dist/cjs/helpers/prettyFormatZeroQuery.native.js.map +1 -1
- package/dist/cjs/helpers/queryContext.cjs +2 -2
- package/dist/cjs/helpers/queryContext.native.js +4 -4
- package/dist/cjs/helpers/queryContext.native.js.map +1 -1
- package/dist/cjs/helpers/recoverZeroClient.cjs +14 -15
- package/dist/cjs/helpers/recoverZeroClient.native.js +26 -23
- package/dist/cjs/helpers/recoverZeroClient.native.js.map +1 -1
- package/dist/cjs/helpers/recoverZeroClient.test.cjs +10 -8
- package/dist/cjs/helpers/recoverZeroClient.test.native.js +17 -21
- package/dist/cjs/helpers/recoverZeroClient.test.native.js.map +1 -1
- package/dist/cjs/helpers/sleep.cjs +34 -0
- package/dist/cjs/helpers/sleep.native.js +39 -0
- package/dist/cjs/helpers/sleep.native.js.map +1 -0
- package/dist/cjs/helpers/time.cjs +47 -0
- package/dist/cjs/helpers/time.native.js +80 -0
- package/dist/cjs/helpers/time.native.js.map +1 -0
- package/dist/cjs/helpers/tuple.cjs +18 -0
- package/dist/cjs/helpers/tuple.native.js +21 -0
- package/dist/cjs/helpers/tuple.native.js.map +1 -0
- package/dist/cjs/helpers/useMutation.native.js +5 -5
- package/dist/cjs/helpers/useMutation.native.js.map +1 -1
- package/dist/cjs/helpers/useMutation.test.native.js +1 -1
- package/dist/cjs/helpers/useMutation.test.native.js.map +1 -1
- package/dist/cjs/helpers/useZeroDebug.cjs +2 -2
- package/dist/cjs/helpers/useZeroDebug.native.js +7 -8
- package/dist/cjs/helpers/useZeroDebug.native.js.map +1 -1
- package/dist/cjs/index.cjs +9 -1
- package/dist/cjs/index.native.js +8 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/instanceRegistry.cjs +16 -4
- package/dist/cjs/instanceRegistry.native.js +56 -6
- package/dist/cjs/instanceRegistry.native.js.map +1 -1
- package/dist/cjs/multi.cjs +34 -13
- package/dist/cjs/multi.native.js +39 -17
- package/dist/cjs/multi.native.js.map +1 -1
- package/dist/cjs/multiInstance.test.cjs +6 -2
- package/dist/cjs/multiInstance.test.native.js +13 -9
- package/dist/cjs/multiInstance.test.native.js.map +1 -1
- package/dist/cjs/multiInstanceNested.test.native.js +10 -10
- package/dist/cjs/multiInstanceNested.test.native.js.map +1 -1
- package/dist/cjs/multiPartition.test.cjs +85 -36
- package/dist/cjs/multiPartition.test.native.js +102 -39
- package/dist/cjs/multiPartition.test.native.js.map +1 -1
- package/dist/cjs/mutations.cjs +2 -2
- package/dist/cjs/mutations.native.js +4 -4
- package/dist/cjs/mutations.native.js.map +1 -1
- package/dist/cjs/mutations.test.native.js +3 -3
- package/dist/cjs/mutations.test.native.js.map +1 -1
- package/dist/cjs/provideZero.hydration.test.cjs +3 -3
- package/dist/cjs/provideZero.hydration.test.native.js +4 -4
- package/dist/cjs/provideZero.hydration.test.native.js.map +1 -1
- package/dist/cjs/run.cjs +1 -1
- package/dist/cjs/run.native.js +3 -3
- package/dist/cjs/run.native.js.map +1 -1
- package/dist/cjs/server.cjs +20 -16
- package/dist/cjs/server.native.js +50 -57
- package/dist/cjs/server.native.js.map +1 -1
- package/dist/cjs/server.test.cjs +129 -6
- package/dist/cjs/server.test.native.js +141 -10
- package/dist/cjs/server.test.native.js.map +1 -1
- package/dist/cjs/serverWhere.test.native.js +3 -3
- package/dist/cjs/serverWhere.test.native.js.map +1 -1
- package/dist/cjs/state.cjs +2 -2
- package/dist/cjs/state.native.js +10 -10
- package/dist/cjs/state.native.js.map +1 -1
- package/dist/cjs/usePermission.test.native.js +3 -3
- package/dist/cjs/usePermission.test.native.js.map +1 -1
- package/dist/cjs/useQuery.empty.test.cjs +3 -3
- package/dist/cjs/useQuery.empty.test.native.js +6 -6
- package/dist/cjs/useQuery.empty.test.native.js.map +1 -1
- package/dist/cjs/useQuery.ssr.test.native.js +2 -2
- package/dist/cjs/useQuery.ssr.test.native.js.map +1 -1
- package/dist/cjs/vite-plugin.cjs +7 -8
- package/dist/cjs/vite-plugin.native.js +11 -11
- package/dist/cjs/vite-plugin.native.js.map +1 -1
- package/dist/cjs/where.cjs +3 -3
- package/dist/cjs/where.native.js +4 -4
- package/dist/cjs/where.native.js.map +1 -1
- package/dist/esm/cli.mjs +6 -3
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/cli.native.js +6 -3
- package/dist/esm/cli.native.js.map +1 -1
- package/dist/esm/combineZeroClients.mjs +1 -1
- package/dist/esm/combineZeroClients.mjs.map +1 -1
- package/dist/esm/combineZeroClients.native.js +5 -5
- package/dist/esm/combineZeroClients.native.js.map +1 -1
- package/dist/esm/config.mjs +5 -0
- package/dist/esm/config.mjs.map +1 -0
- package/dist/esm/config.native.js +5 -0
- package/dist/esm/config.native.js.map +1 -0
- package/dist/esm/createPermissions.mjs +1 -1
- package/dist/esm/createPermissions.mjs.map +1 -1
- package/dist/esm/createPermissions.native.js +16 -26
- package/dist/esm/createPermissions.native.js.map +1 -1
- package/dist/esm/createUseQuery.mjs +2 -2
- package/dist/esm/createUseQuery.mjs.map +1 -1
- package/dist/esm/createUseQuery.native.js +3 -3
- package/dist/esm/createUseQuery.native.js.map +1 -1
- package/dist/esm/createUseQueryDirect.mjs +2 -1
- package/dist/esm/createUseQueryDirect.mjs.map +1 -1
- package/dist/esm/createUseQueryDirect.native.js +12 -16
- package/dist/esm/createUseQueryDirect.native.js.map +1 -1
- package/dist/esm/createZeroClient.connection.test.mjs +11 -0
- package/dist/esm/createZeroClient.connection.test.mjs.map +1 -1
- package/dist/esm/createZeroClient.connection.test.native.js +14 -7
- package/dist/esm/createZeroClient.connection.test.native.js.map +1 -1
- package/dist/esm/createZeroClient.mjs +25 -5
- package/dist/esm/createZeroClient.mjs.map +1 -1
- package/dist/esm/createZeroClient.native.js +54 -28
- package/dist/esm/createZeroClient.native.js.map +1 -1
- package/dist/esm/createZeroClient.recovery.test.mjs +90 -3
- package/dist/esm/createZeroClient.recovery.test.mjs.map +1 -1
- package/dist/esm/createZeroClient.recovery.test.native.js +109 -19
- package/dist/esm/createZeroClient.recovery.test.native.js.map +1 -1
- package/dist/esm/generate-helpers.mjs +70 -18
- package/dist/esm/generate-helpers.mjs.map +1 -1
- package/dist/esm/generate-helpers.native.js +110 -24
- package/dist/esm/generate-helpers.native.js.map +1 -1
- package/dist/esm/generate-layout.mjs +560 -0
- package/dist/esm/generate-layout.mjs.map +1 -0
- package/dist/esm/generate-layout.native.js +1246 -0
- package/dist/esm/generate-layout.native.js.map +1 -0
- package/dist/esm/generate-lite.mjs +296 -28
- package/dist/esm/generate-lite.mjs.map +1 -1
- package/dist/esm/generate-lite.native.js +855 -103
- package/dist/esm/generate-lite.native.js.map +1 -1
- package/dist/esm/generate-lite.test.mjs +434 -42
- package/dist/esm/generate-lite.test.mjs.map +1 -1
- package/dist/esm/generate-lite.test.native.js +447 -42
- package/dist/esm/generate-lite.test.native.js.map +1 -1
- package/dist/esm/generate.mjs +172 -96
- package/dist/esm/generate.mjs.map +1 -1
- package/dist/esm/generate.native.js +324 -150
- package/dist/esm/generate.native.js.map +1 -1
- package/dist/esm/generate.test.mjs +366 -49
- package/dist/esm/generate.test.mjs.map +1 -1
- package/dist/esm/generate.test.native.js +378 -49
- package/dist/esm/generate.test.native.js.map +1 -1
- package/dist/esm/helpers/NullToOptional.mjs +2 -0
- package/dist/esm/helpers/NullToOptional.mjs.map +1 -0
- package/dist/esm/helpers/NullToOptional.native.js +2 -0
- package/dist/esm/helpers/NullToOptional.native.js.map +1 -0
- package/dist/esm/helpers/asyncContext.mjs +102 -0
- package/dist/esm/helpers/asyncContext.mjs.map +1 -0
- package/dist/esm/helpers/asyncContext.native.js +58 -0
- package/dist/esm/helpers/asyncContext.native.js.map +1 -0
- package/dist/esm/helpers/batchQuery.mjs +1 -1
- package/dist/esm/helpers/batchQuery.native.js +1 -1
- package/dist/esm/helpers/createMutators.mjs +5 -2
- package/dist/esm/helpers/createMutators.mjs.map +1 -1
- package/dist/esm/helpers/createMutators.native.js +10 -7
- package/dist/esm/helpers/createMutators.native.js.map +1 -1
- package/dist/esm/helpers/createMutators.test.native.js +2 -2
- package/dist/esm/helpers/createMutators.test.native.js.map +1 -1
- package/dist/esm/helpers/ellipsis.mjs +10 -0
- package/dist/esm/helpers/ellipsis.mjs.map +1 -0
- package/dist/esm/helpers/ellipsis.native.js +10 -0
- package/dist/esm/helpers/ellipsis.native.js.map +1 -0
- package/dist/esm/helpers/emitter.mjs +72 -0
- package/dist/esm/helpers/emitter.mjs.map +1 -0
- package/dist/esm/helpers/emitter.native.js +95 -0
- package/dist/esm/helpers/emitter.native.js.map +1 -0
- package/dist/esm/helpers/ensure.mjs +8 -0
- package/dist/esm/helpers/ensure.mjs.map +1 -0
- package/dist/esm/helpers/ensure.native.js +9 -0
- package/dist/esm/helpers/ensure.native.js.map +1 -0
- package/dist/esm/helpers/ensureLoggedIn.mjs +1 -1
- package/dist/esm/helpers/ensureLoggedIn.mjs.map +1 -1
- package/dist/esm/helpers/ensureLoggedIn.native.js +2 -2
- package/dist/esm/helpers/ensureLoggedIn.native.js.map +1 -1
- package/dist/esm/helpers/errors.mjs +14 -0
- package/dist/esm/helpers/errors.mjs.map +1 -0
- package/dist/esm/helpers/errors.native.js +123 -0
- package/dist/esm/helpers/errors.native.js.map +1 -0
- package/dist/esm/helpers/getCurrentComponentStack.mjs +35 -0
- package/dist/esm/helpers/getCurrentComponentStack.mjs.map +1 -0
- package/dist/esm/helpers/getCurrentComponentStack.native.js +54 -0
- package/dist/esm/helpers/getCurrentComponentStack.native.js.map +1 -0
- package/dist/esm/helpers/globalValue.mjs +14 -0
- package/dist/esm/helpers/globalValue.mjs.map +1 -0
- package/dist/esm/helpers/globalValue.native.js +14 -0
- package/dist/esm/helpers/globalValue.native.js.map +1 -0
- package/dist/esm/helpers/mapObject.mjs +11 -0
- package/dist/esm/helpers/mapObject.mjs.map +1 -0
- package/dist/esm/helpers/mapObject.native.js +11 -0
- package/dist/esm/helpers/mapObject.native.js.map +1 -0
- package/dist/esm/helpers/mutationLifecycle.mjs +221 -0
- package/dist/esm/helpers/mutationLifecycle.mjs.map +1 -0
- package/dist/esm/helpers/mutationLifecycle.native.js +359 -0
- package/dist/esm/helpers/mutationLifecycle.native.js.map +1 -0
- package/dist/esm/helpers/mutationLifecycle.test.mjs +175 -0
- package/dist/esm/helpers/mutationLifecycle.test.mjs.map +1 -0
- package/dist/esm/helpers/mutationLifecycle.test.native.js +180 -0
- package/dist/esm/helpers/mutationLifecycle.test.native.js.map +1 -0
- package/dist/esm/helpers/mutatorContext.mjs +1 -1
- package/dist/esm/helpers/mutatorContext.native.js +4 -4
- package/dist/esm/helpers/mutatorContext.native.js.map +1 -1
- package/dist/esm/helpers/platform.mjs +5 -0
- package/dist/esm/helpers/platform.mjs.map +1 -0
- package/dist/esm/helpers/platform.native.js +4 -0
- package/dist/esm/helpers/platform.native.js.map +1 -0
- package/dist/esm/helpers/prettyFormatZeroQuery.mjs +1 -1
- package/dist/esm/helpers/prettyFormatZeroQuery.native.js +6 -6
- package/dist/esm/helpers/prettyFormatZeroQuery.native.js.map +1 -1
- package/dist/esm/helpers/queryContext.mjs +1 -1
- package/dist/esm/helpers/queryContext.native.js +3 -3
- package/dist/esm/helpers/queryContext.native.js.map +1 -1
- package/dist/esm/helpers/recoverZeroClient.mjs +14 -15
- package/dist/esm/helpers/recoverZeroClient.mjs.map +1 -1
- package/dist/esm/helpers/recoverZeroClient.native.js +26 -23
- package/dist/esm/helpers/recoverZeroClient.native.js.map +1 -1
- package/dist/esm/helpers/recoverZeroClient.test.mjs +8 -6
- package/dist/esm/helpers/recoverZeroClient.test.mjs.map +1 -1
- package/dist/esm/helpers/recoverZeroClient.test.native.js +15 -19
- package/dist/esm/helpers/recoverZeroClient.test.native.js.map +1 -1
- package/dist/esm/helpers/sleep.mjs +9 -0
- package/dist/esm/helpers/sleep.mjs.map +1 -0
- package/dist/esm/helpers/sleep.native.js +11 -0
- package/dist/esm/helpers/sleep.native.js.map +1 -0
- package/dist/esm/helpers/time.mjs +22 -0
- package/dist/esm/helpers/time.mjs.map +1 -0
- package/dist/esm/helpers/time.native.js +52 -0
- package/dist/esm/helpers/time.native.js.map +1 -0
- package/dist/esm/helpers/tuple.mjs +2 -0
- package/dist/esm/helpers/tuple.mjs.map +1 -0
- package/dist/esm/helpers/tuple.native.js +2 -0
- package/dist/esm/helpers/tuple.native.js.map +1 -0
- package/dist/esm/helpers/useMutation.native.js +5 -5
- package/dist/esm/helpers/useMutation.native.js.map +1 -1
- package/dist/esm/helpers/useMutation.test.native.js +1 -1
- package/dist/esm/helpers/useMutation.test.native.js.map +1 -1
- package/dist/esm/helpers/useZeroDebug.mjs +1 -1
- package/dist/esm/helpers/useZeroDebug.mjs.map +1 -1
- package/dist/esm/helpers/useZeroDebug.native.js +6 -7
- package/dist/esm/helpers/useZeroDebug.native.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +3 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +3 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/instanceRegistry.mjs +13 -2
- package/dist/esm/instanceRegistry.mjs.map +1 -1
- package/dist/esm/instanceRegistry.native.js +53 -4
- package/dist/esm/instanceRegistry.native.js.map +1 -1
- package/dist/esm/multi.mjs +33 -12
- package/dist/esm/multi.mjs.map +1 -1
- package/dist/esm/multi.native.js +38 -16
- package/dist/esm/multi.native.js.map +1 -1
- package/dist/esm/multiInstance.test.mjs +5 -1
- package/dist/esm/multiInstance.test.mjs.map +1 -1
- package/dist/esm/multiInstance.test.native.js +12 -8
- package/dist/esm/multiInstance.test.native.js.map +1 -1
- package/dist/esm/multiInstanceNested.test.native.js +10 -10
- package/dist/esm/multiInstanceNested.test.native.js.map +1 -1
- package/dist/esm/multiPartition.test.mjs +86 -37
- package/dist/esm/multiPartition.test.mjs.map +1 -1
- package/dist/esm/multiPartition.test.native.js +103 -40
- package/dist/esm/multiPartition.test.native.js.map +1 -1
- package/dist/esm/mutations.mjs +1 -1
- package/dist/esm/mutations.mjs.map +1 -1
- package/dist/esm/mutations.native.js +3 -3
- package/dist/esm/mutations.native.js.map +1 -1
- package/dist/esm/mutations.test.native.js +3 -3
- package/dist/esm/mutations.test.native.js.map +1 -1
- package/dist/esm/provideZero.hydration.test.mjs +1 -1
- package/dist/esm/provideZero.hydration.test.mjs.map +1 -1
- package/dist/esm/provideZero.hydration.test.native.js +2 -2
- package/dist/esm/provideZero.hydration.test.native.js.map +1 -1
- package/dist/esm/run.mjs +1 -1
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/run.native.js +3 -3
- package/dist/esm/run.native.js.map +1 -1
- package/dist/esm/server.mjs +20 -15
- package/dist/esm/server.mjs.map +1 -1
- package/dist/esm/server.native.js +50 -56
- package/dist/esm/server.native.js.map +1 -1
- package/dist/esm/server.test.mjs +130 -7
- package/dist/esm/server.test.mjs.map +1 -1
- package/dist/esm/server.test.native.js +142 -11
- package/dist/esm/server.test.native.js.map +1 -1
- package/dist/esm/serverWhere.test.mjs.map +1 -1
- package/dist/esm/serverWhere.test.native.js +3 -3
- package/dist/esm/serverWhere.test.native.js.map +1 -1
- package/dist/esm/state.mjs +1 -1
- package/dist/esm/state.mjs.map +1 -1
- package/dist/esm/state.native.js +9 -9
- package/dist/esm/state.native.js.map +1 -1
- package/dist/esm/usePermission.test.native.js +3 -3
- package/dist/esm/usePermission.test.native.js.map +1 -1
- package/dist/esm/useQuery.empty.test.mjs +1 -1
- package/dist/esm/useQuery.empty.test.mjs.map +1 -1
- package/dist/esm/useQuery.empty.test.native.js +4 -4
- package/dist/esm/useQuery.empty.test.native.js.map +1 -1
- package/dist/esm/useQuery.ssr.test.native.js +2 -2
- package/dist/esm/useQuery.ssr.test.native.js.map +1 -1
- package/dist/esm/vite-plugin.mjs +7 -8
- package/dist/esm/vite-plugin.mjs.map +1 -1
- package/dist/esm/vite-plugin.native.js +11 -11
- package/dist/esm/vite-plugin.native.js.map +1 -1
- package/dist/esm/where.mjs +1 -1
- package/dist/esm/where.mjs.map +1 -1
- package/dist/esm/where.native.js +2 -2
- package/dist/esm/where.native.js.map +1 -1
- package/package.json +18 -6
- package/src/cli.ts +11 -3
- package/src/combineZeroClients.tsx +18 -10
- package/src/config.ts +72 -0
- package/src/createPermissions.ts +3 -4
- package/src/createUseQuery.tsx +8 -8
- package/src/createUseQueryDirect.tsx +11 -10
- package/src/createZeroClient.authData.test.tsx +1 -1
- package/src/createZeroClient.connection.test.tsx +13 -2
- package/src/createZeroClient.recovery.test.tsx +100 -4
- package/src/createZeroClient.tsx +87 -30
- package/src/generate-helpers.ts +108 -31
- package/src/generate-layout.ts +841 -0
- package/src/generate-lite.test.ts +421 -42
- package/src/generate-lite.ts +479 -43
- package/src/generate.test.ts +552 -90
- package/src/generate.ts +360 -163
- package/src/helpers/NullToOptional.ts +5 -0
- package/src/helpers/asyncContext.native.ts +90 -0
- package/src/helpers/asyncContext.ts +159 -0
- package/src/helpers/batchQuery.ts +2 -2
- package/src/helpers/createMutators.ts +11 -8
- package/src/helpers/ellipsis.ts +9 -0
- package/src/helpers/emitter.tsx +106 -0
- package/src/helpers/ensure.ts +10 -0
- package/src/helpers/ensureLoggedIn.ts +1 -2
- package/src/helpers/errors.ts +13 -0
- package/src/helpers/getCurrentComponentStack.ts +56 -0
- package/src/helpers/globalValue.ts +40 -0
- package/src/helpers/mapObject.ts +14 -0
- package/src/helpers/mutationLifecycle.test.ts +180 -0
- package/src/helpers/mutationLifecycle.ts +313 -0
- package/src/helpers/mutatorContext.ts +3 -3
- package/src/helpers/platform.native.ts +2 -0
- package/src/helpers/platform.ts +16 -0
- package/src/helpers/prettyFormatZeroQuery.ts +2 -2
- package/src/helpers/queryContext.ts +2 -2
- package/src/helpers/recoverZeroClient.test.ts +23 -21
- package/src/helpers/recoverZeroClient.ts +56 -49
- package/src/helpers/sleep.ts +8 -0
- package/src/helpers/time.ts +51 -0
- package/src/helpers/tuple.ts +1 -0
- package/src/helpers/useMutation.tsx +3 -3
- package/src/helpers/useZeroDebug.ts +3 -3
- package/src/index.ts +13 -1
- package/src/instanceRegistry.ts +19 -6
- package/src/multi.ts +75 -31
- package/src/multiInstance.test.tsx +31 -13
- package/src/multiInstanceNested.test.tsx +2 -2
- package/src/multiPartition.test.ts +62 -27
- package/src/mutations.ts +10 -10
- package/src/provideZero.hydration.test.tsx +1 -1
- package/src/resolveQuery.ts +2 -2
- package/src/run.ts +7 -7
- package/src/server.test.ts +117 -18
- package/src/server.ts +70 -43
- package/src/serverWhere.ts +2 -2
- package/src/state.ts +2 -2
- package/src/types.ts +47 -6
- package/src/useQuery.empty.test.tsx +3 -3
- package/src/useQuery.ssr.test.ts +1 -1
- package/src/vite-plugin.ts +12 -9
- package/src/where.ts +6 -7
- package/src/zeroRunner.ts +2 -2
- package/types/combineZeroClients.d.ts +11 -5
- package/types/combineZeroClients.d.ts.map +1 -1
- package/types/config.d.ts +62 -0
- package/types/config.d.ts.map +1 -0
- package/types/createPermissions.d.ts.map +1 -1
- package/types/createUseQuery.d.ts.map +1 -1
- package/types/createUseQueryDirect.d.ts +1 -1
- package/types/createUseQueryDirect.d.ts.map +1 -1
- package/types/createZeroClient.d.ts +22 -12
- package/types/createZeroClient.d.ts.map +1 -1
- package/types/createZeroClient.recovery.test.d.ts.map +1 -1
- package/types/generate-helpers.d.ts +17 -2
- package/types/generate-helpers.d.ts.map +1 -1
- package/types/generate-layout.d.ts +26 -0
- package/types/generate-layout.d.ts.map +1 -0
- package/types/generate-lite.d.ts +29 -1
- package/types/generate-lite.d.ts.map +1 -1
- package/types/generate.d.ts +20 -0
- package/types/generate.d.ts.map +1 -1
- package/types/helpers/NullToOptional.d.ts +6 -0
- package/types/helpers/NullToOptional.d.ts.map +1 -0
- package/types/helpers/asyncContext.d.ts +15 -0
- package/types/helpers/asyncContext.d.ts.map +1 -0
- package/types/helpers/asyncContext.native.d.ts +8 -0
- package/types/helpers/asyncContext.native.d.ts.map +1 -0
- package/types/helpers/createMutators.d.ts +2 -1
- package/types/helpers/createMutators.d.ts.map +1 -1
- package/types/helpers/ellipsis.d.ts +2 -0
- package/types/helpers/ellipsis.d.ts.map +1 -0
- package/types/helpers/emitter.d.ts +21 -0
- package/types/helpers/emitter.d.ts.map +1 -0
- package/types/helpers/ensure.d.ts +2 -0
- package/types/helpers/ensure.d.ts.map +1 -0
- package/types/helpers/ensureLoggedIn.d.ts.map +1 -1
- package/types/helpers/errors.d.ts +7 -0
- package/types/helpers/errors.d.ts.map +1 -0
- package/types/helpers/getCurrentComponentStack.d.ts +2 -0
- package/types/helpers/getCurrentComponentStack.d.ts.map +1 -0
- package/types/helpers/globalValue.d.ts +23 -0
- package/types/helpers/globalValue.d.ts.map +1 -0
- package/types/helpers/mapObject.d.ts +4 -0
- package/types/helpers/mapObject.d.ts.map +1 -0
- package/types/helpers/mutationLifecycle.d.ts +44 -0
- package/types/helpers/mutationLifecycle.d.ts.map +1 -0
- package/types/helpers/mutationLifecycle.test.d.ts +2 -0
- package/types/helpers/mutationLifecycle.test.d.ts.map +1 -0
- package/types/helpers/platform.d.ts +3 -0
- package/types/helpers/platform.d.ts.map +1 -0
- package/types/helpers/platform.native.d.ts +3 -0
- package/types/helpers/platform.native.d.ts.map +1 -0
- package/types/helpers/recoverZeroClient.d.ts +13 -9
- package/types/helpers/recoverZeroClient.d.ts.map +1 -1
- package/types/helpers/sleep.d.ts +2 -0
- package/types/helpers/sleep.d.ts.map +1 -0
- package/types/helpers/time.d.ts +28 -0
- package/types/helpers/time.d.ts.map +1 -0
- package/types/helpers/tuple.d.ts +2 -0
- package/types/helpers/tuple.d.ts.map +1 -0
- package/types/index.d.ts +3 -1
- package/types/index.d.ts.map +1 -1
- package/types/instanceRegistry.d.ts +1 -0
- package/types/instanceRegistry.d.ts.map +1 -1
- package/types/multi.d.ts +23 -1
- package/types/multi.d.ts.map +1 -1
- package/types/mutations.d.ts +1 -1
- package/types/mutations.d.ts.map +1 -1
- package/types/run.d.ts.map +1 -1
- package/types/server.d.ts +12 -8
- package/types/server.d.ts.map +1 -1
- package/types/state.d.ts +1 -1
- package/types/state.d.ts.map +1 -1
- package/types/types.d.ts +23 -1
- package/types/types.d.ts.map +1 -1
- package/types/vite-plugin.d.ts.map +1 -1
- package/types/where.d.ts.map +1 -1
- package/lint/ssr-guards.js +0 -165
- package/test-fixtures/server-return-types.ts +0 -58
- package/test-fixtures/tsconfig.json +0 -13
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -8
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createMutationLifecycle,
|
|
5
|
+
MutationTimeoutError,
|
|
6
|
+
StaleGenerationError,
|
|
7
|
+
} from './mutationLifecycle'
|
|
8
|
+
|
|
9
|
+
function deferred<T>() {
|
|
10
|
+
let resolve!: (value: T | PromiseLike<T>) => void
|
|
11
|
+
let reject!: (error: unknown) => void
|
|
12
|
+
const promise = new Promise<T>((resolvePromise, rejectPromise) => {
|
|
13
|
+
resolve = resolvePromise
|
|
14
|
+
reject = rejectPromise
|
|
15
|
+
})
|
|
16
|
+
return { promise, reject, resolve }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function setup(threshold = 2) {
|
|
20
|
+
const recoverFromAckTimeout = vi.fn()
|
|
21
|
+
const lifecycle = createMutationLifecycle({
|
|
22
|
+
ackTimeoutRecoveryThreshold: threshold,
|
|
23
|
+
recoverFromAckTimeout,
|
|
24
|
+
})
|
|
25
|
+
lifecycle.activate()
|
|
26
|
+
return { lifecycle, recoverFromAckTimeout }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.restoreAllMocks()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('mutation lifecycle', () => {
|
|
34
|
+
test('extracts typed mutation errors and times out each settlement phase', async () => {
|
|
35
|
+
vi.useFakeTimers()
|
|
36
|
+
try {
|
|
37
|
+
const { lifecycle } = setup()
|
|
38
|
+
await expect(
|
|
39
|
+
lifecycle.awaitMutationClient(
|
|
40
|
+
{ client: Promise.resolve({ type: 'error', error: { message: 'denied' } }) },
|
|
41
|
+
'save note'
|
|
42
|
+
)
|
|
43
|
+
).rejects.toMatchObject({
|
|
44
|
+
name: 'MutationResultError',
|
|
45
|
+
label: 'save note',
|
|
46
|
+
phase: 'client',
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const waiting = lifecycle.awaitMutationClient(
|
|
50
|
+
{ client: new Promise(() => {}) },
|
|
51
|
+
'stuck note',
|
|
52
|
+
20
|
|
53
|
+
)
|
|
54
|
+
const timedOut = expect(waiting).rejects.toMatchObject({
|
|
55
|
+
name: 'MutationTimeoutError',
|
|
56
|
+
phase: 'client',
|
|
57
|
+
timeoutMs: 20,
|
|
58
|
+
})
|
|
59
|
+
await vi.advanceTimersByTimeAsync(20)
|
|
60
|
+
await timedOut
|
|
61
|
+
} finally {
|
|
62
|
+
vi.useRealTimers()
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('serializes work and skips superseded coalesced writes', async () => {
|
|
67
|
+
const { lifecycle } = setup()
|
|
68
|
+
const blocker = deferred<void>()
|
|
69
|
+
const first = lifecycle.enqueueBackgroundMutation('blocker', () => blocker.promise)
|
|
70
|
+
const superseded = vi.fn()
|
|
71
|
+
const latest = vi.fn(async () => {})
|
|
72
|
+
const oldWrite = lifecycle.enqueueBackgroundMutation('old row', superseded, {
|
|
73
|
+
coalesceKey: 'message:1',
|
|
74
|
+
})
|
|
75
|
+
const newWrite = lifecycle.enqueueBackgroundMutation('new row', latest, {
|
|
76
|
+
coalesceKey: 'message:1',
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
blocker.resolve()
|
|
80
|
+
await Promise.all([first, oldWrite, newWrite])
|
|
81
|
+
expect(superseded).not.toHaveBeenCalled()
|
|
82
|
+
expect(latest).toHaveBeenCalledOnce()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('one failed background mutation does not poison later work', async () => {
|
|
86
|
+
const { lifecycle } = setup()
|
|
87
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
88
|
+
const denied = lifecycle.enqueueBackgroundMutation('stamp', async () => {
|
|
89
|
+
throw new Error('PermissionError: stale task')
|
|
90
|
+
})
|
|
91
|
+
await expect(denied).rejects.toThrow('PermissionError')
|
|
92
|
+
|
|
93
|
+
const next = vi.fn(async () => {})
|
|
94
|
+
await lifecycle.enqueueBackgroundMutation('clear stamp', next)
|
|
95
|
+
expect(next).toHaveBeenCalledOnce()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('recovery or close quietly drops in-flight and queued background work', async () => {
|
|
99
|
+
const { lifecycle } = setup()
|
|
100
|
+
const client = deferred<unknown>()
|
|
101
|
+
const queuedCreate = vi.fn(async () => {})
|
|
102
|
+
const inFlight = lifecycle.enqueueBackgroundMutation('in flight', () => ({
|
|
103
|
+
client: client.promise,
|
|
104
|
+
}))
|
|
105
|
+
const queued = lifecycle.enqueueBackgroundMutation('queued', queuedCreate)
|
|
106
|
+
|
|
107
|
+
lifecycle.fence()
|
|
108
|
+
await expect(inFlight).resolves.toBeUndefined()
|
|
109
|
+
await expect(queued).resolves.toBeUndefined()
|
|
110
|
+
expect(queuedCreate).not.toHaveBeenCalled()
|
|
111
|
+
|
|
112
|
+
const direct = lifecycle.awaitMutationClient(
|
|
113
|
+
{ client: Promise.resolve({}) },
|
|
114
|
+
'direct after close'
|
|
115
|
+
)
|
|
116
|
+
await expect(direct).rejects.toBeInstanceOf(StaleGenerationError)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('recovers only after consecutive server acknowledgement timeouts', async () => {
|
|
120
|
+
vi.useFakeTimers()
|
|
121
|
+
try {
|
|
122
|
+
const { lifecycle, recoverFromAckTimeout } = setup()
|
|
123
|
+
const timeout = async (label: string) => {
|
|
124
|
+
const result = lifecycle.awaitMutationServer(
|
|
125
|
+
{ client: Promise.resolve({}), server: new Promise(() => {}) },
|
|
126
|
+
label,
|
|
127
|
+
10
|
|
128
|
+
)
|
|
129
|
+
const timedOut = expect(result).rejects.toMatchObject({
|
|
130
|
+
name: 'MutationTimeoutError',
|
|
131
|
+
phase: 'server',
|
|
132
|
+
})
|
|
133
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
134
|
+
await timedOut
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await timeout('first')
|
|
138
|
+
expect(recoverFromAckTimeout).not.toHaveBeenCalled()
|
|
139
|
+
await timeout('second')
|
|
140
|
+
expect(recoverFromAckTimeout).toHaveBeenCalledOnce()
|
|
141
|
+
expect(recoverFromAckTimeout).toHaveBeenCalledWith({
|
|
142
|
+
label: 'second',
|
|
143
|
+
timeoutMs: 10,
|
|
144
|
+
consecutiveTimeouts: 2,
|
|
145
|
+
})
|
|
146
|
+
} finally {
|
|
147
|
+
vi.useRealTimers()
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('a server response resets the consecutive timeout count', async () => {
|
|
152
|
+
vi.useFakeTimers()
|
|
153
|
+
try {
|
|
154
|
+
const { lifecycle, recoverFromAckTimeout } = setup()
|
|
155
|
+
const timeout = async () => {
|
|
156
|
+
const result = lifecycle.awaitMutationServer(
|
|
157
|
+
{ client: Promise.resolve({}), server: new Promise(() => {}) },
|
|
158
|
+
'write',
|
|
159
|
+
10
|
|
160
|
+
)
|
|
161
|
+
const timedOut = expect(result).rejects.toBeInstanceOf(MutationTimeoutError)
|
|
162
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
163
|
+
await timedOut
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await timeout()
|
|
167
|
+
await expect(
|
|
168
|
+
lifecycle.awaitMutationServer(
|
|
169
|
+
{ client: Promise.resolve({}), server: Promise.resolve({}) },
|
|
170
|
+
'acked write',
|
|
171
|
+
10
|
|
172
|
+
)
|
|
173
|
+
).resolves.toEqual({})
|
|
174
|
+
await timeout()
|
|
175
|
+
expect(recoverFromAckTimeout).not.toHaveBeenCalled()
|
|
176
|
+
} finally {
|
|
177
|
+
vi.useRealTimers()
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
export type MutationLike = {
|
|
2
|
+
client: Promise<unknown>
|
|
3
|
+
server?: Promise<unknown>
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type BackgroundMutationOptions = {
|
|
7
|
+
coalesceKey?: string
|
|
8
|
+
settle?: 'client' | 'server'
|
|
9
|
+
timeoutMs?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type MutationPhase = 'client' | 'server'
|
|
13
|
+
|
|
14
|
+
export class StaleGenerationError extends Error {
|
|
15
|
+
readonly label: string
|
|
16
|
+
|
|
17
|
+
constructor(label: string) {
|
|
18
|
+
super(`${label} stopped because its Zero instance was replaced or recovered`)
|
|
19
|
+
this.name = 'StaleGenerationError'
|
|
20
|
+
this.label = label
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class MutationTimeoutError extends Error {
|
|
25
|
+
readonly label: string
|
|
26
|
+
readonly phase: MutationPhase
|
|
27
|
+
readonly timeoutMs: number
|
|
28
|
+
|
|
29
|
+
constructor(label: string, phase: MutationPhase, timeoutMs: number) {
|
|
30
|
+
super(
|
|
31
|
+
phase === 'server'
|
|
32
|
+
? `${label} server acknowledgement did not complete within ${timeoutMs}ms`
|
|
33
|
+
: `${label} did not complete within ${timeoutMs}ms`
|
|
34
|
+
)
|
|
35
|
+
this.name = 'MutationTimeoutError'
|
|
36
|
+
this.label = label
|
|
37
|
+
this.phase = phase
|
|
38
|
+
this.timeoutMs = timeoutMs
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class MutationResultError extends Error {
|
|
43
|
+
readonly label: string
|
|
44
|
+
readonly phase: MutationPhase
|
|
45
|
+
readonly result: unknown
|
|
46
|
+
|
|
47
|
+
constructor(label: string, phase: MutationPhase, result: unknown, message: string) {
|
|
48
|
+
super(`${label} failed${phase === 'server' ? ' on server' : ''}: ${message}`)
|
|
49
|
+
this.name = 'MutationResultError'
|
|
50
|
+
this.label = label
|
|
51
|
+
this.phase = phase
|
|
52
|
+
this.result = result
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isStaleGenerationError(error: unknown): error is StaleGenerationError {
|
|
57
|
+
return error instanceof StaleGenerationError
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function mutationErrorMessage(result: unknown): string | null {
|
|
61
|
+
if (!result || typeof result !== 'object') return null
|
|
62
|
+
const typed = result as { type?: unknown; error?: unknown }
|
|
63
|
+
if (typed.type !== 'error') return null
|
|
64
|
+
return describeMutationError(typed.error)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function describeMutationError(error: unknown): string {
|
|
68
|
+
if (error instanceof Error) return error.message
|
|
69
|
+
if (error && typeof error === 'object') {
|
|
70
|
+
const typed = error as { type?: unknown; message?: unknown }
|
|
71
|
+
const parts = [typed.type, typed.message].filter(
|
|
72
|
+
(part): part is string => typeof part === 'string' && part.length > 0
|
|
73
|
+
)
|
|
74
|
+
if (parts.length) return parts.join(': ')
|
|
75
|
+
}
|
|
76
|
+
return String(error)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type GenerationWaiter = {
|
|
80
|
+
reject: () => void
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type BackgroundMutationQueue = {
|
|
84
|
+
tail: Promise<void>
|
|
85
|
+
lastErrorMessage: string | null
|
|
86
|
+
coalesceSequence: Map<string, number>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type MutationLifecycle = ReturnType<typeof createMutationLifecycle>
|
|
90
|
+
|
|
91
|
+
export function createMutationLifecycle(options: {
|
|
92
|
+
ackTimeoutRecoveryThreshold: number
|
|
93
|
+
recoverFromAckTimeout: (input: {
|
|
94
|
+
label: string
|
|
95
|
+
timeoutMs: number
|
|
96
|
+
consecutiveTimeouts: number
|
|
97
|
+
}) => void
|
|
98
|
+
}) {
|
|
99
|
+
const queue: BackgroundMutationQueue = {
|
|
100
|
+
tail: Promise.resolve(),
|
|
101
|
+
lastErrorMessage: null,
|
|
102
|
+
coalesceSequence: new Map(),
|
|
103
|
+
}
|
|
104
|
+
const waiters = new Set<GenerationWaiter>()
|
|
105
|
+
let generation = 0
|
|
106
|
+
let active = false
|
|
107
|
+
let consecutiveServerAckTimeouts = 0
|
|
108
|
+
|
|
109
|
+
function stale(label: string): StaleGenerationError {
|
|
110
|
+
return new StaleGenerationError(label)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fence() {
|
|
114
|
+
if (!active) return
|
|
115
|
+
active = false
|
|
116
|
+
generation += 1
|
|
117
|
+
consecutiveServerAckTimeouts = 0
|
|
118
|
+
queue.coalesceSequence.clear()
|
|
119
|
+
for (const waiter of [...waiters]) {
|
|
120
|
+
waiters.delete(waiter)
|
|
121
|
+
waiter.reject()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function activate() {
|
|
126
|
+
if (active) return
|
|
127
|
+
generation += 1
|
|
128
|
+
active = true
|
|
129
|
+
consecutiveServerAckTimeouts = 0
|
|
130
|
+
queue.lastErrorMessage = null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function observeGeneration(capturedGeneration: number, label: string) {
|
|
134
|
+
let waiter: GenerationWaiter | undefined
|
|
135
|
+
const promise = new Promise<never>((_, reject) => {
|
|
136
|
+
if (!active || capturedGeneration !== generation) {
|
|
137
|
+
reject(stale(label))
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
waiter = {
|
|
141
|
+
reject: () => reject(stale(label)),
|
|
142
|
+
}
|
|
143
|
+
waiters.add(waiter)
|
|
144
|
+
if (!active || capturedGeneration !== generation) {
|
|
145
|
+
waiters.delete(waiter)
|
|
146
|
+
waiter = undefined
|
|
147
|
+
reject(stale(label))
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
return {
|
|
151
|
+
promise,
|
|
152
|
+
dispose() {
|
|
153
|
+
if (waiter) waiters.delete(waiter)
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function awaitInGeneration<T>(input: {
|
|
159
|
+
promise: Promise<T>
|
|
160
|
+
label: string
|
|
161
|
+
phase: MutationPhase
|
|
162
|
+
timeoutMs: number
|
|
163
|
+
generation: number
|
|
164
|
+
}): Promise<T> {
|
|
165
|
+
if (!active || input.generation !== generation) throw stale(input.label)
|
|
166
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
167
|
+
const generationChange = observeGeneration(input.generation, input.label)
|
|
168
|
+
try {
|
|
169
|
+
return await Promise.race([
|
|
170
|
+
input.promise,
|
|
171
|
+
generationChange.promise,
|
|
172
|
+
new Promise<never>((_, reject) => {
|
|
173
|
+
timer = setTimeout(
|
|
174
|
+
() =>
|
|
175
|
+
reject(new MutationTimeoutError(input.label, input.phase, input.timeoutMs)),
|
|
176
|
+
input.timeoutMs
|
|
177
|
+
)
|
|
178
|
+
}),
|
|
179
|
+
])
|
|
180
|
+
} finally {
|
|
181
|
+
generationChange.dispose()
|
|
182
|
+
if (timer) clearTimeout(timer)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function settleMutationClient(
|
|
187
|
+
mutation: MutationLike,
|
|
188
|
+
label: string,
|
|
189
|
+
timeoutMs: number,
|
|
190
|
+
capturedGeneration: number,
|
|
191
|
+
observeServerFailure: boolean
|
|
192
|
+
): Promise<unknown> {
|
|
193
|
+
const result = await awaitInGeneration({
|
|
194
|
+
promise: mutation.client,
|
|
195
|
+
label,
|
|
196
|
+
phase: 'client',
|
|
197
|
+
timeoutMs,
|
|
198
|
+
generation: capturedGeneration,
|
|
199
|
+
})
|
|
200
|
+
const message = mutationErrorMessage(result)
|
|
201
|
+
if (message) throw new MutationResultError(label, 'client', result, message)
|
|
202
|
+
if (observeServerFailure) {
|
|
203
|
+
mutation.server?.catch((error: unknown) => {
|
|
204
|
+
if (!active || capturedGeneration !== generation) return
|
|
205
|
+
console.warn(`[on-zero] ${label} server replication failed:`, error)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
return result
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function awaitMutationClient(
|
|
212
|
+
mutation: MutationLike,
|
|
213
|
+
label: string,
|
|
214
|
+
timeoutMs = 30_000
|
|
215
|
+
): Promise<unknown> {
|
|
216
|
+
return settleMutationClient(mutation, label, timeoutMs, generation, true)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function awaitMutationServer(
|
|
220
|
+
mutation: MutationLike,
|
|
221
|
+
label: string,
|
|
222
|
+
timeoutMs = 30_000
|
|
223
|
+
): Promise<unknown> {
|
|
224
|
+
const capturedGeneration = generation
|
|
225
|
+
const clientResult = await settleMutationClient(
|
|
226
|
+
mutation,
|
|
227
|
+
label,
|
|
228
|
+
timeoutMs,
|
|
229
|
+
capturedGeneration,
|
|
230
|
+
false
|
|
231
|
+
)
|
|
232
|
+
if (!mutation.server) return clientResult
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const serverResult = await awaitInGeneration({
|
|
236
|
+
promise: mutation.server,
|
|
237
|
+
label,
|
|
238
|
+
phase: 'server',
|
|
239
|
+
timeoutMs,
|
|
240
|
+
generation: capturedGeneration,
|
|
241
|
+
})
|
|
242
|
+
consecutiveServerAckTimeouts = 0
|
|
243
|
+
const message = mutationErrorMessage(serverResult)
|
|
244
|
+
if (message) throw new MutationResultError(label, 'server', serverResult, message)
|
|
245
|
+
return serverResult
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error instanceof MutationTimeoutError && error.phase === 'server') {
|
|
248
|
+
consecutiveServerAckTimeouts += 1
|
|
249
|
+
if (consecutiveServerAckTimeouts >= options.ackTimeoutRecoveryThreshold) {
|
|
250
|
+
options.recoverFromAckTimeout({
|
|
251
|
+
label,
|
|
252
|
+
timeoutMs,
|
|
253
|
+
consecutiveTimeouts: consecutiveServerAckTimeouts,
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
throw error
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function enqueueBackgroundMutation(
|
|
262
|
+
label: string,
|
|
263
|
+
create: () => unknown,
|
|
264
|
+
mutationOptions: BackgroundMutationOptions = {}
|
|
265
|
+
): Promise<void> {
|
|
266
|
+
const capturedGeneration = generation
|
|
267
|
+
const { coalesceKey = '', settle = 'client', timeoutMs = 120_000 } = mutationOptions
|
|
268
|
+
const sequence = coalesceKey ? (queue.coalesceSequence.get(coalesceKey) ?? 0) + 1 : 0
|
|
269
|
+
if (coalesceKey) queue.coalesceSequence.set(coalesceKey, sequence)
|
|
270
|
+
|
|
271
|
+
const queued = queue.tail.then(async () => {
|
|
272
|
+
if (!active || capturedGeneration !== generation) throw stale(label)
|
|
273
|
+
if (coalesceKey) {
|
|
274
|
+
if (queue.coalesceSequence.get(coalesceKey) !== sequence) return
|
|
275
|
+
queue.coalesceSequence.delete(coalesceKey)
|
|
276
|
+
}
|
|
277
|
+
const result = await create()
|
|
278
|
+
if (result && typeof result === 'object' && 'client' in result) {
|
|
279
|
+
const mutation = result as MutationLike
|
|
280
|
+
if (settle === 'server' && mutation.server) {
|
|
281
|
+
await awaitMutationServer(mutation, label, timeoutMs)
|
|
282
|
+
} else {
|
|
283
|
+
await settleMutationClient(mutation, label, timeoutMs, capturedGeneration, true)
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
await result
|
|
287
|
+
}
|
|
288
|
+
queue.lastErrorMessage = null
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
const result = queued.catch((error: unknown) => {
|
|
292
|
+
if (isStaleGenerationError(error)) return
|
|
293
|
+
if (!active || capturedGeneration !== generation) return
|
|
294
|
+
const message = describeMutationError(error)
|
|
295
|
+
const alreadyLogged = queue.lastErrorMessage === message
|
|
296
|
+
queue.lastErrorMessage = message
|
|
297
|
+
if (!alreadyLogged) {
|
|
298
|
+
console.warn(`[on-zero] ${label} background mutation failed:`, error)
|
|
299
|
+
}
|
|
300
|
+
throw error
|
|
301
|
+
})
|
|
302
|
+
queue.tail = result.catch(() => {})
|
|
303
|
+
return result
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
activate,
|
|
308
|
+
fence,
|
|
309
|
+
enqueueBackgroundMutation,
|
|
310
|
+
awaitMutationClient,
|
|
311
|
+
awaitMutationServer,
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAsyncContext } from '
|
|
1
|
+
import { createAsyncContext } from './asyncContext'
|
|
2
2
|
|
|
3
3
|
import type { AuthData, MutatorContext } from '../types'
|
|
4
4
|
|
|
@@ -33,7 +33,7 @@ export function isInZeroMutation() {
|
|
|
33
33
|
|
|
34
34
|
export function runWithContext<T>(
|
|
35
35
|
context: MutatorContext,
|
|
36
|
-
fn: () => T | Promise<T
|
|
36
|
+
fn: () => T | Promise<T>
|
|
37
37
|
): Promise<T> {
|
|
38
38
|
return asyncContext.run(context, fn)
|
|
39
39
|
}
|
|
@@ -48,7 +48,7 @@ export function getScopedAuthData(): AuthData | null | undefined {
|
|
|
48
48
|
|
|
49
49
|
export function runWithAuthScope<T>(
|
|
50
50
|
authData: AuthData | null,
|
|
51
|
-
fn: () => T | Promise<T
|
|
51
|
+
fn: () => T | Promise<T>
|
|
52
52
|
): Promise<T> {
|
|
53
53
|
return authScopeContext.run(authData, fn)
|
|
54
54
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const IS_SERVER_RUNTIME: boolean =
|
|
2
|
+
process.env.VITE_ENVIRONMENT === 'ssr' ||
|
|
3
|
+
(process.env.VITE_ENVIRONMENT !== 'client' &&
|
|
4
|
+
typeof process !== 'undefined' &&
|
|
5
|
+
!!process.versions &&
|
|
6
|
+
!!(process.versions.node || (process.versions as any).bun))
|
|
7
|
+
|
|
8
|
+
const isBrowser =
|
|
9
|
+
process.env.VITE_ENVIRONMENT === 'client' ||
|
|
10
|
+
(process.env.VITE_ENVIRONMENT !== 'ssr' &&
|
|
11
|
+
typeof navigator !== 'undefined' &&
|
|
12
|
+
typeof location !== 'undefined')
|
|
13
|
+
|
|
14
|
+
export const IS_SERVER: boolean =
|
|
15
|
+
process.env.VITE_ENVIRONMENT === 'ssr' ||
|
|
16
|
+
(process.env.VITE_ENVIRONMENT !== 'client' && !isBrowser)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ellipsis } from '
|
|
1
|
+
import { ellipsis } from './ellipsis'
|
|
2
2
|
|
|
3
3
|
import type { Query } from '@rocicorp/zero'
|
|
4
4
|
|
|
5
5
|
export const prettyFormatZeroQuery = (
|
|
6
6
|
query: Query<any, any, any>,
|
|
7
|
-
mode: 'full' | 'minimal' = 'full'
|
|
7
|
+
mode: 'full' | 'minimal' = 'full'
|
|
8
8
|
): string => {
|
|
9
9
|
const astObject = query['_completeAst']?.()
|
|
10
10
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAsyncContext } from '
|
|
1
|
+
import { createAsyncContext } from './asyncContext'
|
|
2
2
|
|
|
3
3
|
import type { AuthData } from '../types'
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ export function isInQueryContext() {
|
|
|
14
14
|
|
|
15
15
|
export function runWithQueryContext<T>(
|
|
16
16
|
context: { authData: AuthData | null },
|
|
17
|
-
fn: () => T | Promise<T
|
|
17
|
+
fn: () => T | Promise<T>
|
|
18
18
|
): Promise<T> {
|
|
19
19
|
return asyncContext.run(context, fn)
|
|
20
20
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { createEmitter } from '@take-out/helpers'
|
|
3
1
|
import { UpdateNeededReasonType } from '@rocicorp/zero'
|
|
4
2
|
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
5
3
|
|
|
4
|
+
// @vitest-environment jsdom
|
|
5
|
+
import { createEmitter } from './emitter'
|
|
6
6
|
import {
|
|
7
7
|
classifyZeroRecoveryLog,
|
|
8
8
|
composeRecoveryLogSink,
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
resetRecoveryStateForTests,
|
|
12
12
|
} from './recoverZeroClient'
|
|
13
13
|
|
|
14
|
-
import type { ScheduleReloadContext, ZeroRecoveryDeps } from './recoverZeroClient'
|
|
15
14
|
import type { ZeroEvent } from '../types'
|
|
15
|
+
import type { ScheduleReloadContext, ZeroRecoveryDeps } from './recoverZeroClient'
|
|
16
16
|
|
|
17
17
|
let emitterSeq = 0
|
|
18
18
|
|
|
@@ -48,6 +48,7 @@ describe('zero recovery', () => {
|
|
|
48
48
|
expect(events).toEqual([
|
|
49
49
|
{
|
|
50
50
|
type: 'recovering',
|
|
51
|
+
reasonKey: 'SchemaVersionNotSupported',
|
|
51
52
|
reason: expect.stringContaining('SchemaVersionNotSupported'),
|
|
52
53
|
},
|
|
53
54
|
])
|
|
@@ -208,23 +209,23 @@ describe('zero recovery', () => {
|
|
|
208
209
|
const sqliteError = new Error('This statement has been finalized')
|
|
209
210
|
sqliteError.name = 'SqliteError'
|
|
210
211
|
expect(
|
|
211
|
-
classifyZeroRecoveryLog('error', ['Error running.', sqliteError])
|
|
212
|
+
classifyZeroRecoveryLog('error', ['Error running.', sqliteError])
|
|
212
213
|
).toMatchObject({
|
|
213
|
-
|
|
214
|
+
reasonKey: 'sqlite-statement-finalized',
|
|
214
215
|
dropLocalState: true,
|
|
215
216
|
})
|
|
216
217
|
expect(
|
|
217
218
|
classifyZeroRecoveryLog('error', [
|
|
218
219
|
{ name: 'SqliteError', message: 'database is locked' },
|
|
219
|
-
])
|
|
220
|
+
])
|
|
220
221
|
).toBeUndefined()
|
|
221
222
|
expect(
|
|
222
|
-
classifyZeroRecoveryLog('warn', ['Expected IndexedDB not found'])
|
|
223
|
+
classifyZeroRecoveryLog('warn', ['Expected IndexedDB not found'])
|
|
223
224
|
).toBeUndefined()
|
|
224
225
|
expect(classifyZeroRecoveryLog('error', ['Store is closed'], 10_000)).toBeUndefined()
|
|
225
226
|
expect(classifyZeroRecoveryLog('error', ['Store is closed'], 11_000)).toBeUndefined()
|
|
226
227
|
expect(classifyZeroRecoveryLog('error', ['Store is closed'], 14_000)).toMatchObject({
|
|
227
|
-
|
|
228
|
+
reasonKey: 'store-closed-repeat',
|
|
228
229
|
dropLocalState: true,
|
|
229
230
|
})
|
|
230
231
|
resetRecoveryStateForTests()
|
|
@@ -232,8 +233,8 @@ describe('zero recovery', () => {
|
|
|
232
233
|
classifyZeroRecoveryLog(
|
|
233
234
|
'error',
|
|
234
235
|
['Mutator "send" error on server', 'Store is closed'],
|
|
235
|
-
20_000
|
|
236
|
-
)
|
|
236
|
+
20_000
|
|
237
|
+
)
|
|
237
238
|
).toBeUndefined()
|
|
238
239
|
})
|
|
239
240
|
|
|
@@ -246,7 +247,7 @@ describe('zero recovery', () => {
|
|
|
246
247
|
const events: ZeroEvent[] = []
|
|
247
248
|
const zeroEvents = createEmitter<ZeroEvent | null>(
|
|
248
249
|
`test-recover-${emitterSeq++}`,
|
|
249
|
-
null
|
|
250
|
+
null
|
|
250
251
|
)
|
|
251
252
|
zeroEvents.listen((event) => {
|
|
252
253
|
if (event) events.push(event)
|
|
@@ -272,6 +273,7 @@ describe('zero recovery', () => {
|
|
|
272
273
|
expect(deleteLocalState).toHaveBeenCalledTimes(1)
|
|
273
274
|
expect(events).toContainEqual({
|
|
274
275
|
type: 'recovering',
|
|
276
|
+
reasonKey: 'client-state-not-found',
|
|
275
277
|
reason: 'client state not found',
|
|
276
278
|
})
|
|
277
279
|
})
|
|
@@ -436,23 +438,23 @@ describe('zero recovery', () => {
|
|
|
436
438
|
]
|
|
437
439
|
for (const [args, reason] of cases) {
|
|
438
440
|
expect(classifyZeroRecoveryLog('error', args)).toMatchObject({
|
|
439
|
-
reason,
|
|
441
|
+
reasonKey: reason,
|
|
440
442
|
dropLocalState: true,
|
|
441
443
|
})
|
|
442
444
|
}
|
|
443
|
-
//
|
|
445
|
+
// ack timeouts use the typed mutation path; synthetic strings do not classify.
|
|
444
446
|
expect(
|
|
445
|
-
classifyZeroRecoveryLog('error', ['consecutive server-ack timeouts'])
|
|
447
|
+
classifyZeroRecoveryLog('error', ['consecutive server-ack timeouts'])
|
|
446
448
|
).toBeUndefined()
|
|
447
449
|
expect(
|
|
448
|
-
classifyZeroRecoveryLog('error', ['Connection attempt timed out after 10 seconds'])
|
|
450
|
+
classifyZeroRecoveryLog('error', ['Connection attempt timed out after 10 seconds'])
|
|
449
451
|
).toBeUndefined()
|
|
450
452
|
expect(classifyZeroRecoveryLog('warn', ['ClientNotFound'])).toBeUndefined()
|
|
451
453
|
})
|
|
452
454
|
|
|
453
|
-
test('
|
|
455
|
+
test('benignLogPatterns suppress recovery for matching classified logs', async () => {
|
|
454
456
|
const { deps, deleteLocalState, reload } = setup()
|
|
455
|
-
deps.
|
|
457
|
+
deps.benignLogPatterns = ['ClientNotFound']
|
|
456
458
|
const sink = composeRecoveryLogSink(deps)
|
|
457
459
|
sink.log('error', undefined, 'ClientNotFound: gone')
|
|
458
460
|
await flush()
|
|
@@ -467,13 +469,13 @@ describe('zero recovery', () => {
|
|
|
467
469
|
test('isRecoverableZeroStalePokeMessage matches only the stale-cookie signatures', () => {
|
|
468
470
|
expect(
|
|
469
471
|
isRecoverableZeroStalePokeMessage(
|
|
470
|
-
'Server returned unexpected base cookie during sync'
|
|
471
|
-
)
|
|
472
|
+
'Server returned unexpected base cookie during sync'
|
|
473
|
+
)
|
|
472
474
|
).toBe(true)
|
|
473
475
|
expect(
|
|
474
476
|
isRecoverableZeroStalePokeMessage(
|
|
475
|
-
'Received cookie 5 is < than last snapshot cookie 9, ignoring client view'
|
|
476
|
-
)
|
|
477
|
+
'Received cookie 5 is < than last snapshot cookie 9, ignoring client view'
|
|
478
|
+
)
|
|
477
479
|
).toBe(true)
|
|
478
480
|
expect(isRecoverableZeroStalePokeMessage('client state not found')).toBe(false)
|
|
479
481
|
})
|